diff --git a/.claude/rules/go.md b/.claude/rules/go.md new file mode 100644 index 000000000..5e089e738 --- /dev/null +++ b/.claude/rules/go.md @@ -0,0 +1,11 @@ +--- +paths: + - "**/*.go" +--- + +- Follow standard Go conventions: `gofmt`, exported names have doc comments. +- Error handling: always check and return errors, never ignore with `_`. +- Use the functional options pattern for configuration (see `sdk/go/config.go`). +- Shared packages live in `pkg/` — check there before creating new utilities. +- Test files go in the same package with `_test.go` suffix. +- Run tests from repo root: `go test ./sdk/go/...` (scoped) or `go test ./...` (all). diff --git a/.claude/rules/solidity.md b/.claude/rules/solidity.md new file mode 100644 index 000000000..a0031115e --- /dev/null +++ b/.claude/rules/solidity.md @@ -0,0 +1,12 @@ +--- +paths: + - "contracts/**/*.sol" +--- + +- Foundry project — use `forge build`, `forge test`, `forge fmt`. +- NatSpec comments on all public/external functions. +- Security first: validate all inputs, check for reentrancy, use OpenZeppelin where applicable. +- Test files in `contracts/test/` with `.t.sol` extension. +- Gas optimization matters — avoid unnecessary storage writes. +- Style guide: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-style-guide.md +- Development practices: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-development-practices.md diff --git a/.claude/rules/typescript.md b/.claude/rules/typescript.md new file mode 100644 index 000000000..d06745203 --- /dev/null +++ b/.claude/rules/typescript.md @@ -0,0 +1,13 @@ +--- +paths: + - "sdk/**/*.ts" + - "sdk/**/*.tsx" +--- + +- Use `const` by default, `let` only when reassignment is needed. No `var`. +- Prefer `viem` over `ethers.js` for Ethereum interactions in production code. +- All public API functions must be exported through the barrel `index.ts`. +- Use strict TypeScript — no `any` unless absolutely unavoidable (e.g., RPC wire types). +- Async functions preferred over raw Promise chains. +- Test files use `.test.ts` extension (not `.spec.ts`). +- When modifying sdk-compat: never barrel re-export SDK classes (SSR risk). Only `export type` is safe. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..851a6798d --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,35 @@ +{ + "permissions": { + "allow": [ + "Bash(npm test*)", + "Bash(npm run build*)", + "Bash(npm run typecheck*)", + "Bash(npm run lint*)", + "Bash(npm run clean*)", + "Bash(npx jest*)", + "Bash(npx prettier*)", + "Bash(npx tsx *)", + "Bash(go test *)", + "Bash(go build *)", + "Bash(go vet *)", + "Bash(go run *)", + "Bash(forge build*)", + "Bash(forge test*)", + "Bash(forge fmt*)", + "Bash(git status*)", + "Bash(git log*)", + "Bash(git diff*)", + "Bash(git branch*)", + "Bash(gh pr *)", + "Bash(gh issue *)", + "Bash(gh run *)" + ], + "deny": [ + "Bash(git push --force*)", + "Bash(git reset --hard*)", + "Bash(rm -rf *)", + "Bash(*>.env*)", + "Bash(npm publish*)" + ] + } +} diff --git a/.claude/skills/build-sdk/SKILL.md b/.claude/skills/build-sdk/SKILL.md new file mode 100644 index 000000000..43f6700e1 --- /dev/null +++ b/.claude/skills/build-sdk/SKILL.md @@ -0,0 +1,21 @@ +--- +name: build-sdk +description: Build SDK packages in the correct dependency order +allowed-tools: Bash, Read +--- + +Build SDK packages for: $ARGUMENTS + +**Important:** sdk/ts-compat depends on sdk/ts via `"@yellow-org/sdk": "file:../ts"`. Always respect build order. + +1. If $ARGUMENTS is "all" or empty, build everything in order: + - `cd sdk/ts && npm run build` (this runs tests then tsc) + - `cd sdk/ts-compat && npm run build` + - `go build ./sdk/go/...` + +2. If $ARGUMENTS specifies a single package, build just that: + - "ts" -> `cd sdk/ts && npm run build` + - "ts-compat" or "compat" -> check sdk/ts/dist/ exists first; if not, build sdk/ts first, then `cd sdk/ts-compat && npm run build` + - "go" -> `go build ./sdk/go/...` + +3. Report build success/failure for each package with any error details. diff --git a/.claude/skills/lint/SKILL.md b/.claude/skills/lint/SKILL.md new file mode 100644 index 000000000..aa96754e2 --- /dev/null +++ b/.claude/skills/lint/SKILL.md @@ -0,0 +1,18 @@ +--- +name: lint +description: Run linters and formatters for the appropriate language +allowed-tools: Bash, Read +--- + +Lint and check formatting for: $ARGUMENTS + +Route to the correct linter based on context: + +- **sdk/ts/** -> `cd sdk/ts && npm run lint && npx prettier --check .` +- **sdk/ts-compat/** -> `cd sdk/ts-compat && npm run typecheck` (no dedicated lint script; typecheck is the closest) +- **contracts/** -> `cd contracts && forge fmt --check` +- **Go code** -> `go vet ./...` from repo root (or scope to specific path like `./sdk/go/...`) + +If no arguments provided, run all applicable linters and report a summary. + +Report any issues found. Suggest fixes but do not auto-apply unless the user asks. diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md new file mode 100644 index 000000000..4126b2082 --- /dev/null +++ b/.claude/skills/review-pr/SKILL.md @@ -0,0 +1,27 @@ +--- +name: review-pr +description: Thorough code review of current changes or a specific PR +allowed-tools: Bash, Read, Grep, Glob +--- + +Review changes for: $ARGUMENTS + +1. **Get the diff:** + - If $ARGUMENTS is a PR number: `gh pr diff $ARGUMENTS` + - If no arguments: `git diff` and `git diff --staged` for uncommitted changes + +2. **For each changed file, check:** + - **Correctness:** Does the logic do what it claims? Any off-by-one errors, race conditions, or wrong assumptions? + - **Types:** Are TypeScript types accurate and strict? Are Go errors properly handled? + - **Security:** No exposed secrets, no injection vectors, no reentrancy (contracts), no XSS + - **Tests:** Are new functions tested? Are edge cases and error paths covered? + - **Exports:** If public API changed, is the barrel `index.ts` updated? (sdk-compat: no SSR-unsafe re-exports) + - **Docs:** If behavior changed, are README/CLAUDE.md/migration docs updated? + - **Style:** Consistent with existing patterns in the file and project conventions + +3. **Categorize findings:** + - **CRITICAL:** Must fix before merge (bugs, security issues, data loss risk) + - **WARNING:** Should fix (missing tests, poor error handling, incomplete docs) + - **SUGGESTION:** Nice to have (naming improvements, minor refactors, style tweaks) + +4. **Give an overall verdict:** APPROVE / REQUEST CHANGES / COMMENT diff --git a/.claude/skills/test/SKILL.md b/.claude/skills/test/SKILL.md new file mode 100644 index 000000000..fa90befc2 --- /dev/null +++ b/.claude/skills/test/SKILL.md @@ -0,0 +1,28 @@ +--- +name: test +description: Run tests for a specific module or the whole project +allowed-tools: Bash, Read, Grep, Glob +--- + +Run tests for: $ARGUMENTS + +Detect the target module and run the appropriate test command: + +1. If $ARGUMENTS names a specific file or directory, use that to determine the module +2. If no arguments, detect from the current working directory or recently edited files + +Route to the correct test runner: +- **sdk/ts/** -> `cd sdk/ts && npm test` +- **sdk/ts-compat/** -> `cd sdk/ts-compat && npm test` +- **sdk/go/** or Go files under sdk/go/ -> `go test ./sdk/go/... -v` (from repo root) +- **contracts/** or .sol files -> `cd contracts && forge test` +- **nitronode/** -> `go test ./nitronode/... -v` (from repo root) +- **test/integration/** -> `cd test/integration && npm test` +- **Repo root with no argument** -> run `go test ./...` (Go packages only — does NOT cover `sdk/ts`, `sdk/ts-compat`, `contracts`, or `test/integration`). Ask the user to specify a target for non-Go tests. + +If a specific test file is given (e.g., `sdk/ts/test/unit/utils.test.ts`), run only that file: +- For Jest: `cd sdk/ts && npx jest test/unit/utils.test.ts` +- For Go: resolve the file's containing package, then run `go test ./sdk/go/ -run '^TestName$'` +- For Forge: `cd contracts && forge test --match-path test/MyTest.t.sol` + +Report results: pass count, fail count, and any error details. diff --git a/.claude/skills/typecheck/SKILL.md b/.claude/skills/typecheck/SKILL.md new file mode 100644 index 000000000..2abf44f7a --- /dev/null +++ b/.claude/skills/typecheck/SKILL.md @@ -0,0 +1,18 @@ +--- +name: typecheck +description: Run TypeScript type checking across SDK packages +allowed-tools: Bash, Read +--- + +Run typecheck for: $ARGUMENTS + +1. If $ARGUMENTS specifies a package ("ts", "ts-compat", or "both"), check that package +2. If no arguments, check both TypeScript packages: + +```bash +cd sdk/ts && npm run typecheck +cd sdk/ts-compat && npm run typecheck +``` + +3. Report any type errors with file paths and line numbers +4. If the user asks about Go type checking, suggest `go vet ./...` instead diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 000000000..15759f870 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,82 @@ +# Nitrolite — Cursor Rules + +## What This Project Is + +Nitrolite is a state channel protocol for Ethereum/EVM blockchains. Off-chain instant transactions with on-chain security. The repo contains: Solidity contracts (Foundry), a Go nitronode broker (renamed from clearnode), TypeScript + Go SDKs, and MCP servers for AI tooling. + +## Architecture + +- **Channels**: State containers between a User and a Node. Hold asset allocations, support off-chain state updates. +- **States**: Versioned allocations. Each new state = previous version + 1. Mutually signed states are enforceable on-chain. +- **Nitronode** (formerly Clearnode): Off-chain broker. WebSocket JSON-RPC. Wire format: `{ "req": [REQUEST_ID, METHOD, PARAMS, TIMESTAMP], "sig": ["SIGNATURE"] }` +- **App Sessions**: Multi-party extensions on channels with quorum-based governance (weight + threshold voting). + +## TypeScript SDK Conventions + +- `@yellow-org/sdk` = v1 protocol SDK (use for all new code) +- `@yellow-org/sdk-compat` = bridges 0.5.x API surface to v1 runtime (migration only, wraps Client with NitroliteClient) +- V1 API source of truth: `docs/api.yaml`. Do NOT reference `docs/legacy/API.md` for v1 methods (it has 0.5.x names) +- Use `const` by default, `let` only when reassignment is needed. No `var`. +- Prefer `viem` over `ethers.js` for Ethereum interactions. +- All public API functions exported through barrel `index.ts`. +- Strict TypeScript — no `any` unless unavoidable (e.g., RPC wire types). +- Async functions preferred over raw Promise chains. +- Tests: `.test.ts` extension, Jest. Run: `cd sdk/ts && npm test` +- sdk-compat: NEVER barrel re-export SDK classes (SSR risk). Only `export type` is safe. +- Build order: `sdk/ts` must build before `sdk/ts-compat`. + +## Go SDK Conventions + +- Module: `github.com/layer-3/nitrolite` (root go.mod, Go 1.25) +- Follow standard Go: `gofmt`, exported names have doc comments. +- Error handling: always check and return errors, never ignore with `_`. +- Functional options pattern for configuration (see `sdk/go/config.go`). +- Shared packages in `pkg/` — check there before creating new utilities. +- Tests: `_test.go` suffix, same package. Run from repo root: `go test ./sdk/go/...` +- Use `context.Context` for all async operations, `github.com/shopspring/decimal` for amounts. + +## Solidity Conventions + +- Foundry project: `forge build`, `forge test`, `forge fmt`. +- NatSpec comments on all public/external functions. +- Security first: validate inputs, check reentrancy, use OpenZeppelin. +- Tests in `contracts/test/` with `.t.sol` extension. +- Gas optimization matters — avoid unnecessary storage writes. +- Style guide: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-style-guide.md +- Development practices: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-development-practices.md + +## Key SDK Methods (Both TS and Go) + +- `Deposit(chainId, asset, amount)` — deposit funds, creates channel if needed +- `Transfer(recipient, asset, amount)` — off-chain instant transfer +- `Checkpoint(asset)` — submit co-signed state to blockchain (required after deposit, withdraw, close) +- `CloseHomeChannel(asset)` — prepare finalize state (follow with Checkpoint) +- `CreateAppSession(definition, sessionData, quorumSigs)` — create multi-party session +- `SubmitAppState(appStateUpdate, quorumSigs)` — submit app state (Operate, Withdraw, or Close intent) +- `GetChannels(wallet)`, `GetBalances(wallet)`, `GetConfig()` — queries + +> No `CloseAppSession()` exists on the SDK Client. Close via `SubmitAppState` with Close intent. + +## Key Reference Files + +- Protocol description: `protocol-description.md` +- Smart contract invariants: `contracts/SECURITY.md` +- Main SC entrypoint: `contracts/src/ChannelHub.sol` (design: `contracts/suggested-contract-design.md`) +- Nitronode logic: `nitronode/README.md`, `docs/legacy/` +- Advanced protocol docs: `docs/protocol/` + +## Commit Convention + +``` +feat|fix|chore|test|docs(scope): description +``` +Examples: `feat(sdk/ts): add transfer batching`, `fix(sdk-compat): export missing type` + +## Common Mistakes to Avoid + +- Don't use `ethers.js` — use `viem` for Ethereum interactions +- Don't ignore Go errors with `_` +- Don't barrel re-export classes in sdk-compat (causes SSR failures) +- Don't run `npm test && npm run build` in sdk/ts (double-tests; build already runs tests) +- Don't edit `.env` files or commit secrets +- Don't create new utilities without checking `pkg/` first (Go) diff --git a/.dockerignore b/.dockerignore index 72c49dfbb..8e6a2167c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,21 +1,17 @@ .dockerignore .gitignore .gitmodules -docker-compose.yml .git/ .github/ LICENSE contracts/ docs/ sdk/ts/ -test/ *.md -# clearnode -clearnode/Dockerfile -clearnode/.gitignore -clearnode/docs/ -clearnode/chart/ -clearnode/scripts/ -clearnode/docker-compose.yml +# nitronode +nitronode/Dockerfile +nitronode/.gitignore +nitronode/docs/ +nitronode/chart/ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b93cc0637..cc3b4bf09 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ # CODEOWNERS: https://help.github.com/articles/about-codeowners/ # Yellow Network - Research and Development -* @alessio @dimast-x @nksazonov @philanton @ihsraham +* @dimast-x @nksazonov @philanton @ihsraham diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..38b6b4337 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,61 @@ +# Nitrolite Copilot Instructions + +Nitrolite is a state channel protocol for Ethereum/EVM blockchains — off-chain instant transactions with on-chain security. Repo contains: Solidity contracts (Foundry), Go nitronode broker (renamed from clearnode), TypeScript + Go SDKs. + +## Architecture + +- Channels: state containers between User and Node, hold asset allocations +- States: versioned allocations, each version = previous + 1, mutually signed = enforceable on-chain +- Nitronode (formerly Clearnode): off-chain broker, WebSocket JSON-RPC wire format +- App Sessions: multi-party extensions with quorum-based governance + +## TypeScript SDK (`@yellow-org/sdk`) + +- `@yellow-org/sdk` = v1 protocol SDK. Use for all new code. +- `@yellow-org/sdk-compat` = bridges 0.5.x API to v1 runtime. Migration only. Wraps Client with NitroliteClient. +- V1 API source of truth: `docs/api.yaml`. `docs/legacy/API.md` has 0.5.x method names. +- Use `const` by default, `viem` over `ethers.js`, strict TypeScript (no `any`) +- All public API through barrel `index.ts`, async/await preferred +- Tests: `.test.ts`, Jest. Run: `cd sdk/ts && npm test` +- Build order: `sdk/ts` before `sdk/ts-compat` +- sdk-compat: NEVER barrel re-export classes (SSR risk), only `export type` + +## Go SDK (`github.com/layer-3/nitrolite/sdk/go`) + +- Root go.mod, Go 1.25. Standard Go: `gofmt`, doc comments on exports +- Always check errors, never ignore with `_` +- Functional options pattern (`sdk/go/config.go`), shared utils in `pkg/` +- Tests: `_test.go`, run from repo root: `go test ./sdk/go/...` +- Use `context.Context`, `github.com/shopspring/decimal` for amounts + +## Solidity (`contracts/`) + +- Foundry: `forge build`, `forge test`, `forge fmt` +- NatSpec on public/external functions, security-first, OpenZeppelin +- Tests: `.t.sol` in `contracts/test/` +- Style: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-style-guide.md +- Practices: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-development-practices.md + +## Key SDK Methods (both TS and Go) + +Deposit, Transfer, Checkpoint (required after deposit/withdraw/close), CloseHomeChannel (follow with Checkpoint), CreateAppSession (definition, sessionData, quorumSigs), SubmitAppState (appStateUpdate, quorumSigs — use Close intent to close sessions), GetChannels(wallet), GetBalances(wallet), GetConfig + +> No CloseAppSession() on SDK Client. Close via SubmitAppState with Close intent. + +## Key Reference Files + +- Protocol description: `protocol-description.md` +- Smart contract invariants: `contracts/SECURITY.md` +- Main SC entrypoint: `contracts/src/ChannelHub.sol` (design: `contracts/suggested-contract-design.md`) +- Nitronode logic: `nitronode/README.md`, `docs/legacy/` +- Advanced protocol docs: `docs/protocol/` + +## Commits + +`feat|fix|chore|test|docs(scope): description` + +## Avoid + +- ethers.js (use viem), ignoring Go errors, barrel re-exporting classes in sdk-compat +- Running `npm test && npm run build` in sdk/ts (build already runs tests) +- Editing .env files, committing secrets diff --git a/.github/workflows/build-node-project.yml b/.github/workflows/build-node-project.yml index c4f0047a8..9652cd06f 100644 --- a/.github/workflows/build-node-project.yml +++ b/.github/workflows/build-node-project.yml @@ -11,6 +11,11 @@ on: description: 'Human-readable name for the project' required: true type: string + bootstrap-project-path: + description: 'Optional sibling project that must be built before the target (e.g. for `file:` dependencies). The bootstrap project is always built with `npm run build:ci`.' + required: false + type: string + default: '' jobs: build: @@ -26,7 +31,19 @@ jobs: with: node-version-file: '${{ inputs.project-path }}/package.json' cache: 'npm' - cache-dependency-path: '${{ inputs.project-path }}/package-lock.json' + cache-dependency-path: | + ${{ inputs.project-path }}/package-lock.json + ${{ inputs.bootstrap-project-path != '' && format('{0}/package-lock.json', inputs.bootstrap-project-path) || '' }} + + - name: Install bootstrap dependencies + if: ${{ inputs.bootstrap-project-path != '' }} + run: npm ci + working-directory: ${{ inputs.bootstrap-project-path }} + + - name: Build bootstrap project + if: ${{ inputs.bootstrap-project-path != '' }} + run: npm run build:ci + working-directory: ${{ inputs.bootstrap-project-path }} - name: Install dependencies run: npm ci diff --git a/.github/workflows/deploy-clearnode.yaml b/.github/workflows/deploy-nitronode.yaml similarity index 88% rename from .github/workflows/deploy-clearnode.yaml rename to .github/workflows/deploy-nitronode.yaml index 00aa50326..9ae998994 100644 --- a/.github/workflows/deploy-clearnode.yaml +++ b/.github/workflows/deploy-nitronode.yaml @@ -1,4 +1,4 @@ -name: Deploy Clearnode +name: Deploy Nitronode on: workflow_call: @@ -21,7 +21,7 @@ on: jobs: deploy: - name: Deploy Clearnode + name: Deploy Nitronode runs-on: ubuntu-latest permissions: contents: read @@ -32,7 +32,7 @@ jobs: GKE_BASTION_NAME: gke-uat-bastion GKE_BASTION_ZONE: europe-central2-a HELM_NAMESPACE: 'clearnet-${{ inputs.target-env }}' - HELM_RELEASE: clearnode + HELM_RELEASE: nitronode HELM_CONFIG_ENV: '${{ inputs.target-env }}' KUBECONFIG: kubeconfig.conf @@ -88,10 +88,10 @@ jobs: exit 1 fi - helm upgrade -i ${HELM_RELEASE} clearnode/chart \ + helm upgrade -i ${HELM_RELEASE} nitronode/chart \ -n ${HELM_NAMESPACE} \ --reuse-values \ --set image.tag=${IMAGE_TAG} \ - -f clearnode/chart/config/${HELM_CONFIG_ENV}/clearnode.yaml \ - --set-file=config.blockchains=clearnode/chart/config/${HELM_CONFIG_ENV}/blockchains.yaml \ - --set-file=config.assets=clearnode/chart/config/${HELM_CONFIG_ENV}/assets.yaml + -f nitronode/chart/config/${HELM_CONFIG_ENV}/nitronode.yaml \ + --set-file=config.blockchains=nitronode/chart/config/${HELM_CONFIG_ENV}/blockchains.yaml \ + --set-file=config.assets=nitronode/chart/config/${HELM_CONFIG_ENV}/assets.yaml diff --git a/.github/workflows/main-pr.yml b/.github/workflows/main-pr.yml index 96d4b3ab3..faeb86dca 100644 --- a/.github/workflows/main-pr.yml +++ b/.github/workflows/main-pr.yml @@ -8,24 +8,134 @@ permissions: contents: read jobs: - test-clearnode: - name: Test (Clearnode) + test-nitronode: + name: Test (Nitronode) uses: ./.github/workflows/test-go.yml test-forge: name: Test (Foundry) uses: ./.github/workflows/test-forge.yml - # test-sdk: - # name: Test (SDK) - # uses: ./.github/workflows/test-sdk.yml - # with: - # project-path: 'sdk' - # project-name: 'SDK' + test-sdk-ts: + name: Test (TS SDK) + uses: ./.github/workflows/test-sdk.yml + with: + project-path: 'sdk/ts' + project-name: 'TS SDK' + forge-build: true - build-and-publish-clearnode: - name: Build and Publish (Clearnode) - needs: test-clearnode + test-sdk-compat: + name: Test (TS SDK Compat) + uses: ./.github/workflows/test-sdk.yml + with: + project-path: 'sdk/ts-compat' + project-name: 'TS SDK Compat' + bootstrap-project-path: 'sdk/ts' + + test-playground: + name: Test (Playground) + uses: ./.github/workflows/build-node-project.yml + with: + project-path: 'playground' + project-name: 'Playground' + bootstrap-project-path: 'sdk/ts' + + test-protocol-drift-static: + name: Test (Protocol Drift Static) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.5.1 + + - name: Build contract artifacts + run: forge build + working-directory: contracts + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: sdk/ts/package.json + cache: npm + cache-dependency-path: | + sdk/ts/package-lock.json + sdk/ts-compat/package-lock.json + + - name: Install TS SDK dependencies + run: npm ci + working-directory: sdk/ts + + - name: Build TS SDK for compat file dependency + run: npm run build:ci + working-directory: sdk/ts + + - name: Install TS SDK Compat dependencies + run: npm ci + working-directory: sdk/ts-compat + + - name: Run static protocol drift checks + run: ./scripts/check-protocol-drift.sh --static + + test-protocol-drift-runtime: + name: Test (Protocol Drift Runtime) + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: sdk/ts/package.json + cache: npm + cache-dependency-path: | + sdk/ts/package-lock.json + sdk/ts-compat/package-lock.json + + - name: Install TS SDK dependencies + run: npm ci + working-directory: sdk/ts + + - name: Build TS SDK for compat file dependency + run: npm run build:ci + working-directory: sdk/ts + + - name: Install TS SDK Compat dependencies + run: npm ci + working-directory: sdk/ts-compat + + - name: Run runtime protocol drift smoke + run: ./scripts/check-protocol-drift.sh --runtime + env: + NITRONODE_RUNTIME_SMOKE_LOG_DIR: runtime-smoke-logs + + - name: Upload runtime smoke logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: protocol-drift-runtime-smoke-logs + path: runtime-smoke-logs + if-no-files-found: ignore + + build-and-publish-nitronode: + name: Build and Publish (Nitronode) + needs: test-nitronode runs-on: ubuntu-latest permissions: contents: read @@ -49,11 +159,77 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./clearnode/Dockerfile + file: ./nitronode/Dockerfile push: true tags: | - ghcr.io/${{ github.repository }}/clearnode:${{ steps.sha.outputs.short_sha }} + ghcr.io/${{ github.repository }}/nitronode:${{ steps.sha.outputs.short_sha }} cache-from: type=gha build-args: | VERSION=${{ steps.sha.outputs.short_sha }} + build-and-publish-faucet: + name: Build and Publish (Faucet) + needs: test-nitronode + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v6 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract short SHA + id: sha + run: echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./faucet-app/server/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/faucet-app:${{ steps.sha.outputs.short_sha }} + cache-from: type=gha + + build-and-publish-playground: + name: Build and Publish (Playground) + needs: test-playground + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract short SHA + id: sha + run: echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./playground/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/playground:${{ steps.sha.outputs.short_sha }} + cache-from: type=gha diff --git a/.github/workflows/main-push.yml b/.github/workflows/main-push.yml index 901393272..968488bb8 100644 --- a/.github/workflows/main-push.yml +++ b/.github/workflows/main-push.yml @@ -8,31 +8,141 @@ permissions: contents: read jobs: - test-clearnode: - name: Test (Clearnode) + test-nitronode: + name: Test (Nitronode) uses: ./.github/workflows/test-go.yml test-forge: name: Test (Foundry) uses: ./.github/workflows/test-forge.yml - # test-sdk: - # name: Test (SDK) - # uses: ./.github/workflows/test-sdk.yml - # with: - # project-path: 'sdk' - # project-name: 'SDK' + test-sdk-ts: + name: Test (TS SDK) + uses: ./.github/workflows/test-sdk.yml + with: + project-path: 'sdk/ts' + project-name: 'TS SDK' + forge-build: true + + test-sdk-compat: + name: Test (TS SDK Compat) + uses: ./.github/workflows/test-sdk.yml + with: + project-path: 'sdk/ts-compat' + project-name: 'TS SDK Compat' + bootstrap-project-path: 'sdk/ts' + + test-playground: + name: Test (Playground) + uses: ./.github/workflows/build-node-project.yml + with: + project-path: 'playground' + project-name: 'Playground' + bootstrap-project-path: 'sdk/ts' + + test-protocol-drift-static: + name: Test (Protocol Drift Static) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.5.1 + + - name: Build contract artifacts + run: forge build + working-directory: contracts + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: sdk/ts/package.json + cache: npm + cache-dependency-path: | + sdk/ts/package-lock.json + sdk/ts-compat/package-lock.json + + - name: Install TS SDK dependencies + run: npm ci + working-directory: sdk/ts + + - name: Build TS SDK for compat file dependency + run: npm run build:ci + working-directory: sdk/ts + + - name: Install TS SDK Compat dependencies + run: npm ci + working-directory: sdk/ts-compat + + - name: Run static protocol drift checks + run: ./scripts/check-protocol-drift.sh --static + + test-protocol-drift-runtime: + name: Test (Protocol Drift Runtime) + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: sdk/ts/package.json + cache: npm + cache-dependency-path: | + sdk/ts/package-lock.json + sdk/ts-compat/package-lock.json + + - name: Install TS SDK dependencies + run: npm ci + working-directory: sdk/ts + + - name: Build TS SDK for compat file dependency + run: npm run build:ci + working-directory: sdk/ts + + - name: Install TS SDK Compat dependencies + run: npm ci + working-directory: sdk/ts-compat + + - name: Run runtime protocol drift smoke + run: ./scripts/check-protocol-drift.sh --runtime + env: + NITRONODE_RUNTIME_SMOKE_LOG_DIR: runtime-smoke-logs + + - name: Upload runtime smoke logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: protocol-drift-runtime-smoke-logs + path: runtime-smoke-logs + if-no-files-found: ignore # build-and-publish-sdk: - # needs: test-sdk + # needs: [test-sdk-ts, test-sdk-compat] # name: Build and Publish (SDK) # uses: ./.github/workflows/publish-sdk.yml # secrets: # npm-token: ${{ secrets.NPM_TOKEN }} - build-and-publish-clearnode: - name: Build and Publish (Clearnode) - needs: test-clearnode + build-and-publish-nitronode: + name: Build and Publish (Nitronode) + needs: test-nitronode runs-on: ubuntu-latest permissions: contents: read @@ -64,17 +174,109 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./clearnode/Dockerfile + file: ./nitronode/Dockerfile push: true tags: | - ghcr.io/${{ github.repository }}/clearnode:${{ steps.sha.outputs.short_sha }} - ghcr.io/${{ github.repository }}/clearnode:latest-main + ghcr.io/${{ github.repository }}/nitronode:${{ steps.sha.outputs.short_sha }} + ghcr.io/${{ github.repository }}/nitronode:latest-main cache-from: type=gha cache-to: type=gha,mode=max - + + build-and-publish-faucet: + name: Build and Publish (Faucet) + needs: test-nitronode + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.tagger.outputs.image_tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract short SHA + id: sha + run: echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Save image tag + id: tagger + run: echo "image_tag=${{ steps.sha.outputs.short_sha }}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./faucet-app/server/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/faucet-app:${{ steps.sha.outputs.short_sha }} + ghcr.io/${{ github.repository }}/faucet-app:latest-main + cache-from: type=gha + cache-to: type=gha,mode=max + + build-and-publish-playground: + name: Build and Publish (Playground) + needs: test-playground + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.tagger.outputs.image_tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract short SHA + id: sha + run: echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Save image tag + id: tagger + run: echo "image_tag=${{ steps.sha.outputs.short_sha }}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./playground/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/playground:${{ steps.sha.outputs.short_sha }} + ghcr.io/${{ github.repository }}/playground:latest-main + cache-from: type=gha + cache-to: type=gha,mode=max + # deploy-prod: # name: Deploy to Prod - # uses: ./.github/workflows/deploy-clearnode.yaml + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: prod # secrets: @@ -83,7 +285,7 @@ jobs: # deploy-sandbox: # name: Deploy to Sandbox - # uses: ./.github/workflows/deploy-clearnode.yaml + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: sandbox # secrets: @@ -93,7 +295,7 @@ jobs: # notify-slack: # name: Notify Slack # runs-on: ubuntu-latest - # needs: [deploy-prod, deploy-sandbox, deploy-uat, test-forge, test-sdk] + # needs: [deploy-prod, deploy-sandbox, deploy-uat, test-forge, test-sdk-ts, test-sdk-compat] # if: always() # steps: @@ -105,7 +307,7 @@ jobs: # SLACK_USERNAME: CI/CD Bot # SLACK_COLOR: ${{ contains(needs.*.result, 'failure') && 'failure' || contains(needs.*.result, 'cancelled') && 'cancelled' || 'success' }} # SLACK_ICON_EMOJI: ${{ contains(needs.*.result, 'failure') && ':x:' || contains(needs.*.result, 'cancelled') && ':warning:' || ':white_check_mark:' }} - # SLACK_TITLE: 'Nitrolite Clearnode Release Candidate' + # SLACK_TITLE: 'Nitrolite Nitronode Release Candidate' # SLACK_MESSAGE_ON_SUCCESS: | # ✅ RC build and deployment completed successfully! # ${{github.event.head_commit.message}} @@ -116,4 +318,3 @@ jobs: # ⚠️ RC build or deployment was cancelled! # ${{github.event.head_commit.message}} # SLACK_FOOTER: 'Nitrolite CI/CD Pipeline' - diff --git a/.github/workflows/protocol-drift-external-smoke.yml b/.github/workflows/protocol-drift-external-smoke.yml new file mode 100644 index 000000000..d28befb86 --- /dev/null +++ b/.github/workflows/protocol-drift-external-smoke.yml @@ -0,0 +1,58 @@ +name: Protocol Drift External Smoke + +on: + workflow_dispatch: + inputs: + nitronode_ws_url: + description: Existing Nitronode WebSocket URL to smoke test, for example wss://... + required: true + +permissions: + contents: read + +jobs: + protocol-drift-external-smoke: + name: Protocol Drift External Smoke + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: sdk/ts/package.json + cache: npm + cache-dependency-path: | + sdk/ts/package-lock.json + sdk/ts-compat/package-lock.json + + - name: Install TS SDK dependencies + run: npm ci + working-directory: sdk/ts + + - name: Build TS SDK for compat file dependency + run: npm run build:ci + working-directory: sdk/ts + + - name: Install TS SDK Compat dependencies + run: npm ci + working-directory: sdk/ts-compat + + - name: Run external Nitronode compatibility smoke + run: ./scripts/check-protocol-drift.sh --runtime + env: + NITRONODE_RUNTIME_SMOKE_EXTERNAL: '1' + NITRONODE_RUNTIME_SMOKE_WS_URL: ${{ inputs.nitronode_ws_url }} + NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY: ${{ secrets.NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY }} + NITRONODE_RUNTIME_SMOKE_LOG_DIR: runtime-smoke-logs + + - name: Upload external smoke logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: protocol-drift-external-smoke-logs + path: runtime-smoke-logs + if-no-files-found: ignore diff --git a/.github/workflows/publish-sdk-mcp.yml b/.github/workflows/publish-sdk-mcp.yml new file mode 100644 index 000000000..d97e69acf --- /dev/null +++ b/.github/workflows/publish-sdk-mcp.yml @@ -0,0 +1,264 @@ +name: Publish SDK MCP + +on: + pull_request: + paths: + - '.github/workflows/publish-sdk-mcp.yml' + - 'docs/api.yaml' + - 'docs/protocol/**' + - 'pkg/app/**' + - 'pkg/core/**' + - 'pkg/rpc/**' + - 'sdk/go/**' + - 'sdk/mcp/**' + - 'sdk/ts/**' + - 'sdk/ts-compat/**' + push: + tags: + - 'mcp-v*' + +permissions: + contents: read + +env: + MCP_PUBLISHER_VERSION: v1.7.6 + MCP_PUBLISHER_SHA256_LINUX_AMD64: bcc96ca630cae4cf503b4550bd4a17462d42ad4819273bee56f4385bb059ddee + +jobs: + verify-and-pack: + name: Verify and pack @yellow-org/sdk-mcp + runs-on: ubuntu-latest + outputs: + tarball: ${{ steps.pack.outputs.tarball }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: 'sdk/mcp/package-lock.json' + + - name: Install dependencies + working-directory: ./sdk/mcp + run: npm ci + + - name: Audit production dependencies + working-directory: ./sdk/mcp + run: npm audit --omit=dev --audit-level=moderate + + - name: Typecheck + working-directory: ./sdk/mcp + run: npm run typecheck + + - name: Build package + working-directory: ./sdk/mcp + run: npm run build + + - name: Verify release preflight + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/mcp-v') + working-directory: ./sdk/mcp + run: npm run verify:release-preflight + + - name: Pack package + id: pack + working-directory: ./sdk/mcp + run: | + PACK_JSON="$(npm pack --json)" + printf '%s\n' "$PACK_JSON" > pack.json + TARBALL="$(node -e "const pack = require('./pack.json')[0]; console.log(pack.filename)")" + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + + - name: Adversarial tarball validation + working-directory: ./sdk/mcp + run: npm run verify:package -- pack.json + + - name: Upload package artifact + uses: actions/upload-artifact@v4 + with: + name: sdk-mcp-package + path: | + sdk/mcp/${{ steps.pack.outputs.tarball }} + sdk/mcp/pack.json + sdk/mcp/server.json + if-no-files-found: error + + smoke: + name: Smoke packed MCP server + runs-on: ubuntu-latest + needs: verify-and-pack + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Download package artifact + uses: actions/download-artifact@v4 + with: + name: sdk-mcp-package + path: sdk-mcp-package + + - name: Adversarial runtime smoke test + run: | + set -euo pipefail + SMOKE_DIR="$(mktemp -d)" + mkdir "$SMOKE_DIR/app" + EXPECTED_VERSION="$(node -e "const pack = require('./sdk-mcp-package/pack.json')[0]; console.log(pack.version)")" + TARBALL="${{ needs.verify-and-pack.outputs.tarball }}" + npm --prefix "$SMOKE_DIR/app" install "./sdk-mcp-package/$TARBALL" + BIN="$SMOKE_DIR/app/node_modules/.bin/yellow-sdk-mcp" + cd "$SMOKE_DIR/app" + BIN="$BIN" EXPECTED_VERSION="$EXPECTED_VERSION" node --input-type=module <<'NODE' + import { Client } from '@modelcontextprotocol/sdk/client/index.js'; + import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; + + const expectedVersion = process.env.EXPECTED_VERSION; + const expectedGoVersion = `v${expectedVersion}`; + const transport = new StdioClientTransport({ command: process.env.BIN, args: [] }); + const client = new Client({ name: 'sdk-mcp-smoke', version: '0.0.0' }); + await client.connect(transport); + + const tools = await client.listTools(); + if (tools.tools.length !== 9) { + throw new Error(`expected 9 tools, got ${tools.tools.length}`); + } + if (!tools.tools.some((tool) => tool.name === 'server_info')) { + throw new Error('server_info tool missing'); + } + + const resources = await client.listResources(); + if (resources.resources.length !== 31) { + throw new Error(`expected 31 resources, got ${resources.resources.length}`); + } + + const infoResult = await client.callTool({ name: 'server_info', arguments: {} }); + const info = JSON.parse(infoResult.content[0].text); + if (info.version !== expectedVersion) { + throw new Error(`unexpected server version ${info.version}`); + } + if (info.sdkVersion !== expectedVersion) { + throw new Error(`unexpected sdk version ${info.sdkVersion}`); + } + if (info.compatVersion !== expectedVersion) { + throw new Error(`unexpected compat version ${info.compatVersion}`); + } + if (info.goModuleVersion !== expectedGoVersion) { + throw new Error(`unexpected Go module version ${info.goModuleVersion}`); + } + if (info.contentMode !== 'packaged-snapshot') { + throw new Error(`expected packaged-snapshot mode, got ${info.contentMode}`); + } + if (info.versionPolicy !== 'strict-mirror') { + throw new Error(`expected strict-mirror policy, got ${info.versionPolicy}`); + } + if (!info.sourceCommit || info.sourceCommit === 'unknown') { + throw new Error('sourceCommit must be present in packaged metadata'); + } + if (!Number.isInteger(info.contentManifestFiles) || info.contentManifestFiles < 70) { + throw new Error(`unexpected manifest file count ${info.contentManifestFiles}`); + } + + const tsMethodResult = await client.callTool({ + name: 'lookup_method', + arguments: { name: 'deposit', language: 'typescript' }, + }); + if (!tsMethodResult.content[0].text.includes('deposit')) { + throw new Error('lookup_method did not return packaged TypeScript SDK content'); + } + + const goMethodResult = await client.callTool({ + name: 'lookup_method', + arguments: { name: 'Deposit', language: 'go' }, + }); + if (!goMethodResult.content[0].text.includes('Deposit')) { + throw new Error('lookup_method did not return packaged Go SDK content'); + } + + const typeResult = await client.callTool({ + name: 'lookup_type', + arguments: { name: 'AppDefinitionV1', language: 'both' }, + }); + if (!typeResult.content[0].text.includes('AppDefinitionV1')) { + throw new Error('lookup_type did not return packaged app type content'); + } + + const rpcResult = await client.callTool({ + name: 'lookup_rpc_method', + arguments: { method: 'channels.v1.get_home_channel' }, + }); + if (!rpcResult.content[0].text.includes('channels.v1.get_home_channel')) { + throw new Error('lookup_rpc_method did not return packaged API docs content'); + } + + const scaffoldResult = await client.callTool({ + name: 'scaffold_project', + arguments: { template: 'go-transfer-app' }, + }); + if (!scaffoldResult.content[0].text.includes(`github.com/layer-3/nitrolite ${expectedGoVersion}`)) { + throw new Error('Go scaffold did not use the packaged Go module version'); + } + + await client.close(); + NODE + + publish-npm: + name: Publish npm package + runs-on: ubuntu-latest + needs: + - verify-and-pack + - smoke + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/mcp-v') + environment: mcp-release + permissions: + contents: read + id-token: write + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + + - name: Download package artifact + uses: actions/download-artifact@v4 + with: + name: sdk-mcp-package + path: sdk-mcp-package + + - name: Publish package + run: npm publish "./sdk-mcp-package/${{ needs.verify-and-pack.outputs.tarball }}" --access public --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + publish-registry: + name: Publish MCP Registry metadata + runs-on: ubuntu-latest + needs: publish-npm + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/mcp-v') + environment: mcp-release + permissions: + contents: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install MCP publisher + working-directory: ./sdk/mcp + run: | + ASSET="mcp-publisher_linux_amd64.tar.gz" + curl -fL "https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${ASSET}" -o "$ASSET" + echo "${MCP_PUBLISHER_SHA256_LINUX_AMD64} ${ASSET}" | sha256sum -c - + tar xzf "$ASSET" mcp-publisher + chmod +x mcp-publisher + + - name: Authenticate to MCP Registry + working-directory: ./sdk/mcp + run: ./mcp-publisher login github-oidc + + - name: Publish MCP Registry metadata + working-directory: ./sdk/mcp + run: ./mcp-publisher publish diff --git a/.github/workflows/stable-tag.yml b/.github/workflows/stable-tag.yml index 885b06732..0cda76ca0 100644 --- a/.github/workflows/stable-tag.yml +++ b/.github/workflows/stable-tag.yml @@ -9,8 +9,8 @@ permissions: contents: read jobs: - build-and-publish-clearnode: - name: Build and Publish (Clearnode) + build-and-publish-nitronode: + name: Build and Publish (Nitronode) runs-on: ubuntu-latest permissions: contents: read @@ -42,32 +42,114 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./clearnode/Dockerfile + file: ./nitronode/Dockerfile push: true tags: | - ghcr.io/${{ github.repository }}/clearnode:${{ steps.tagger.outputs.tag }} - ghcr.io/${{ github.repository }}/clearnode:latest + ghcr.io/${{ github.repository }}/nitronode:${{ steps.tagger.outputs.tag }} + ghcr.io/${{ github.repository }}/nitronode:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + build-and-publish-faucet: + name: Build and Publish (Faucet) + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.tagger.outputs.tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract tag name + id: tagger + run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./faucet-app/server/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/faucet-app:${{ steps.tagger.outputs.tag }} + ghcr.io/${{ github.repository }}/faucet-app:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + build-and-publish-playground: + name: Build and Publish (Playground) + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.tagger.outputs.tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract tag name + id: tagger + run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./playground/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/playground:${{ steps.tagger.outputs.tag }} + ghcr.io/${{ github.repository }}/playground:latest cache-from: type=gha cache-to: type=gha,mode=max # deploy-sandbox: # name: Deploy to Sandbox - # needs: build-and-publish-clearnode - # uses: ./.github/workflows/deploy-clearnode.yaml + # needs: build-and-publish-nitronode + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: sandbox - # image-tag: ${{ needs.build-and-publish-clearnode.outputs.image-tag }} + # image-tag: ${{ needs.build-and-publish-nitronode.outputs.image-tag }} # secrets: # gke-project: ${{ secrets.GKE_PROJECT }} # gke-sa-key: ${{ secrets.GKE_SANDBOX_SA_KEY }} # deploy-prod: # name: Deploy to Production - # needs: build-and-publish-clearnode - # uses: ./.github/workflows/deploy-clearnode.yaml + # needs: build-and-publish-nitronode + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: prod - # image-tag: ${{ needs.build-and-publish-clearnode.outputs.image-tag }} + # image-tag: ${{ needs.build-and-publish-nitronode.outputs.image-tag }} # secrets: # gke-project: ${{ secrets.GKE_PROJECT }} # gke-sa-key: ${{ secrets.GKE_PROD_SA_KEY }} @@ -75,7 +157,7 @@ jobs: # notify-slack: # name: Notify Slack # runs-on: ubuntu-latest - # needs: [build-and-publish-clearnode, deploy-sandbox, deploy-prod] + # needs: [build-and-publish-nitronode, deploy-sandbox, deploy-prod] # if: always() # steps: @@ -87,7 +169,7 @@ jobs: # SLACK_USERNAME: CI/CD Bot # SLACK_COLOR: ${{ contains(needs.*.result, 'failure') && 'failure' || contains(needs.*.result, 'cancelled') && 'cancelled' || 'success' }} # SLACK_ICON_EMOJI: ${{ contains(needs.*.result, 'failure') && ':x:' || contains(needs.*.result, 'cancelled') && ':warning:' || ':white_check_mark:' }} - # SLACK_TITLE: 'Nitrolite Clearnode Production Release' + # SLACK_TITLE: 'Nitrolite Nitronode Production Release' # SLACK_MESSAGE_ON_SUCCESS: | # ✅ Stable build and deployment completed successfully! # ${{github.event.head_commit.message}} diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml index 55ce9da2a..708017ca7 100644 --- a/.github/workflows/test-go.yml +++ b/.github/workflows/test-go.yml @@ -7,7 +7,7 @@ on: # description: 'Path to the Go project directory' # required: false # type: string - # default: 'clearnode' + # default: 'nitronode' # project-name: # description: 'Human-readable name for the project' # required: false diff --git a/.github/workflows/test-sdk.yml b/.github/workflows/test-sdk.yml index f9afed2a0..da0a6c9c2 100644 --- a/.github/workflows/test-sdk.yml +++ b/.github/workflows/test-sdk.yml @@ -5,35 +5,76 @@ on: inputs: project-path: description: 'Path to the SDK project directory' - required: false + required: true type: string - default: 'sdk' project-name: description: 'Human-readable name for the project' + required: true + type: string + bootstrap-project-path: + description: 'Optional SDK project that must be built before validating the target project' required: false type: string - default: 'SDK' + default: '' + forge-build: + description: 'Build Foundry artifacts before validating this SDK project' + required: false + type: boolean + default: false jobs: test: - name: Test ${{ inputs.project-name }} + name: Validate ${{ inputs.project-name }} runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v6 + with: + submodules: ${{ inputs.forge-build && 'recursive' || 'false' }} + + - name: Install Foundry + if: ${{ inputs.forge-build }} + uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.5.1 + + - name: Build contract artifacts + if: ${{ inputs.forge-build }} + run: forge build + working-directory: contracts - name: Setup Node.js uses: actions/setup-node@v4 with: node-version-file: '${{ inputs.project-path }}/package.json' cache: 'npm' - cache-dependency-path: '${{ inputs.project-path }}/package-lock.json' + cache-dependency-path: | + ${{ inputs.project-path }}/package-lock.json + ${{ inputs.bootstrap-project-path != '' && format('{0}/package-lock.json', inputs.bootstrap-project-path) || '' }} + + - name: Install bootstrap dependencies + if: ${{ inputs.bootstrap-project-path != '' }} + run: npm ci + working-directory: ${{ inputs.bootstrap-project-path }} + + - name: Build bootstrap project + if: ${{ inputs.bootstrap-project-path != '' }} + run: npm run build:ci + working-directory: ${{ inputs.bootstrap-project-path }} - name: Install dependencies run: npm ci working-directory: ${{ inputs.project-path }} - - name: Run tests + - name: Test run: npm test working-directory: ${{ inputs.project-path }} + + - name: Lint + run: npm run lint + working-directory: ${{ inputs.project-path }} + + - name: Build + run: npm run build:ci + working-directory: ${{ inputs.project-path }} diff --git a/.github/workflows/v1-push.yml b/.github/workflows/v1-push.yml index 93af694f7..08693caaa 100644 --- a/.github/workflows/v1-push.yml +++ b/.github/workflows/v1-push.yml @@ -8,23 +8,31 @@ permissions: contents: read jobs: - test-clearnode: - name: Test (Clearnode) + test-nitronode: + name: Test (Nitronode) uses: ./.github/workflows/test-go.yml test-forge: name: Test (Foundry) uses: ./.github/workflows/test-forge.yml - # test-sdk: - # name: Test (SDK) + # test-sdk-ts: + # name: Test (TS SDK) # uses: ./.github/workflows/test-sdk.yml # with: - # project-path: 'sdk' - # project-name: 'SDK' + # project-path: 'sdk/ts' + # project-name: 'TS SDK' + + # test-sdk-compat: + # name: Test (TS SDK Compat) + # uses: ./.github/workflows/test-sdk.yml + # with: + # project-path: 'sdk/ts-compat' + # project-name: 'TS SDK Compat' + # bootstrap-project-path: 'sdk/ts' # build-and-publish-sdk: - # needs: test-sdk + # needs: [test-sdk-ts, test-sdk-compat] # name: Build and Publish (SDK) # uses: ./.github/workflows/publish-sdk.yml # secrets: @@ -32,7 +40,7 @@ jobs: auto-tag: name: Auto-Tag - needs: test-clearnode + needs: test-nitronode runs-on: ubuntu-latest permissions: contents: write @@ -51,7 +59,7 @@ jobs: git config user.name "GitHub Actions" git config user.email "github-actions@github.com" - ./clearnode/scripts/auto_tag.sh v1.0.0 + ./nitronode/scripts/auto_tag.sh v1.0.0 - name: Store the new tag id: tagger @@ -59,8 +67,8 @@ jobs: NEW_TAG=$(git describe --tags --match "v[0-9]*.[0-9]*.[0-9]*-rc.[0-9]*" --abbrev=0) echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT - build-and-publish-clearnode: - name: Build and Publish (Clearnode) + build-and-publish-nitronode: + name: Build and Publish (Nitronode) needs: auto-tag runs-on: ubuntu-latest permissions: @@ -94,20 +102,106 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./clearnode/Dockerfile + file: ./nitronode/Dockerfile push: true tags: | - ghcr.io/${{ github.repository }}/clearnode:${{ needs.auto-tag.outputs.image-tag }} - ghcr.io/${{ github.repository }}/clearnode:latest-rc + ghcr.io/${{ github.repository }}/nitronode:${{ needs.auto-tag.outputs.image-tag }} + ghcr.io/${{ github.repository }}/nitronode:latest-rc cache-from: type=gha cache-to: type=gha,mode=max build-args: | VERSION=${{ needs.auto-tag.outputs.image-tag }} + build-and-publish-faucet: + name: Build and Publish (Faucet) + needs: auto-tag + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.tagger.outputs.image_tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Save image tag + id: tagger + run: | + echo "image_tag=${{ needs.auto-tag.outputs.image-tag }}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./faucet-app/server/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/faucet-app:${{ needs.auto-tag.outputs.image-tag }} + ghcr.io/${{ github.repository }}/faucet-app:latest-rc + cache-from: type=gha + cache-to: type=gha,mode=max + + build-and-publish-playground: + name: Build and Publish (Playground) + needs: auto-tag + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image-tag: ${{ steps.tagger.outputs.image_tag }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Save image tag + id: tagger + run: | + echo "image_tag=${{ needs.auto-tag.outputs.image-tag }}" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./playground/Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}/playground:${{ needs.auto-tag.outputs.image-tag }} + ghcr.io/${{ github.repository }}/playground:latest-rc + cache-from: type=gha + cache-to: type=gha,mode=max + # deploy-prod: # name: Deploy to Prod - # uses: ./.github/workflows/deploy-clearnode.yaml + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: prod # secrets: @@ -116,7 +210,7 @@ jobs: # deploy-sandbox: # name: Deploy to Sandbox - # uses: ./.github/workflows/deploy-clearnode.yaml + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: sandbox # secrets: @@ -125,11 +219,11 @@ jobs: # deploy-uat: # name: Deploy to UAT - # needs: build-and-publish-clearnode - # uses: ./.github/workflows/deploy-clearnode.yaml + # needs: build-and-publish-nitronode + # uses: ./.github/workflows/deploy-nitronode.yaml # with: # target-env: uat - # image-tag: ${{ needs.build-and-publish-clearnode.outputs.image-tag }} + # image-tag: ${{ needs.build-and-publish-nitronode.outputs.image-tag }} # secrets: # gke-project: ${{ secrets.GKE_PROJECT }} # gke-sa-key: ${{ secrets.GKE_UAT_SA_KEY }} @@ -137,7 +231,7 @@ jobs: # notify-slack: # name: Notify Slack # runs-on: ubuntu-latest - # needs: [deploy-prod, deploy-sandbox, deploy-uat, test-forge, test-sdk] + # needs: [deploy-prod, deploy-sandbox, deploy-uat, test-forge, test-sdk-ts, test-sdk-compat] # if: always() # steps: @@ -149,7 +243,7 @@ jobs: # SLACK_USERNAME: CI/CD Bot # SLACK_COLOR: ${{ contains(needs.*.result, 'failure') && 'failure' || contains(needs.*.result, 'cancelled') && 'cancelled' || 'success' }} # SLACK_ICON_EMOJI: ${{ contains(needs.*.result, 'failure') && ':x:' || contains(needs.*.result, 'cancelled') && ':warning:' || ':white_check_mark:' }} - # SLACK_TITLE: 'Nitrolite Clearnode Release Candidate' + # SLACK_TITLE: 'Nitrolite Nitronode Release Candidate' # SLACK_MESSAGE_ON_SUCCESS: | # ✅ RC build and deployment completed successfully! # ${{github.event.head_commit.message}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..094915426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Claude Code (local/personal files) +.claude/settings.local.json +.claude/agent-memory/ + +# OS +.DS_Store + +# Local MCP vetting harness +sdk/mcp-test-results/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..fb3e67a43 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "github": { + "command": "gh", + "args": ["copilot", "mcp"] + }, + "nitrolite": { + "command": "npm", + "args": ["--prefix", "sdk/mcp", "exec", "--", "tsx", "sdk/mcp/src/index.ts"] + } + } +} diff --git a/.windsurfrules b/.windsurfrules new file mode 100644 index 000000000..5a238abdf --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,82 @@ +# Nitrolite — Windsurf Rules + +## What This Project Is + +Nitrolite is a state channel protocol for Ethereum/EVM blockchains. Off-chain instant transactions with on-chain security. The repo contains: Solidity contracts (Foundry), a Go nitronode broker (renamed from clearnode), TypeScript + Go SDKs, and MCP servers for AI tooling. + +## Architecture + +- **Channels**: State containers between a User and a Node. Hold asset allocations, support off-chain state updates. +- **States**: Versioned allocations. Each new state = previous version + 1. Mutually signed states are enforceable on-chain. +- **Nitronode** (formerly Clearnode): Off-chain broker. WebSocket JSON-RPC. Wire format: `{ "req": [REQUEST_ID, METHOD, PARAMS, TIMESTAMP], "sig": ["SIGNATURE"] }` +- **App Sessions**: Multi-party extensions on channels with quorum-based governance (weight + threshold voting). + +## TypeScript SDK Conventions + +- `@yellow-org/sdk` = v1 protocol SDK (use for all new code) +- `@yellow-org/sdk-compat` = bridges 0.5.x API surface to v1 runtime (migration only, wraps Client with NitroliteClient) +- V1 API source of truth: `docs/api.yaml`. Do NOT reference `docs/legacy/API.md` for v1 methods (it has 0.5.x names) +- Use `const` by default, `let` only when reassignment is needed. No `var`. +- Prefer `viem` over `ethers.js` for Ethereum interactions. +- All public API functions exported through barrel `index.ts`. +- Strict TypeScript — no `any` unless unavoidable (e.g., RPC wire types). +- Async functions preferred over raw Promise chains. +- Tests: `.test.ts` extension, Jest. Run: `cd sdk/ts && npm test` +- sdk-compat: NEVER barrel re-export SDK classes (SSR risk). Only `export type` is safe. +- Build order: `sdk/ts` must build before `sdk/ts-compat`. + +## Go SDK Conventions + +- Module: `github.com/layer-3/nitrolite` (root go.mod, Go 1.25) +- Follow standard Go: `gofmt`, exported names have doc comments. +- Error handling: always check and return errors, never ignore with `_`. +- Functional options pattern for configuration (see `sdk/go/config.go`). +- Shared packages in `pkg/` — check there before creating new utilities. +- Tests: `_test.go` suffix, same package. Run from repo root: `go test ./sdk/go/...` +- Use `context.Context` for all async operations, `github.com/shopspring/decimal` for amounts. + +## Solidity Conventions + +- Foundry project: `forge build`, `forge test`, `forge fmt`. +- NatSpec comments on all public/external functions. +- Security first: validate inputs, check reentrancy, use OpenZeppelin. +- Tests in `contracts/test/` with `.t.sol` extension. +- Gas optimization matters — avoid unnecessary storage writes. +- Style guide: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-style-guide.md +- Development practices: https://github.com/layer-3/clearsync/blob/master/contracts/solidity-development-practices.md + +## Key SDK Methods (Both TS and Go) + +- `Deposit(chainId, asset, amount)` — deposit funds, creates channel if needed +- `Transfer(recipient, asset, amount)` — off-chain instant transfer +- `Checkpoint(asset)` — submit co-signed state to blockchain (required after deposit, withdraw, close) +- `CloseHomeChannel(asset)` — prepare finalize state (follow with Checkpoint) +- `CreateAppSession(definition, sessionData, quorumSigs)` — create multi-party session +- `SubmitAppState(appStateUpdate, quorumSigs)` — submit app state (Operate, Withdraw, or Close intent) +- `GetChannels(wallet)`, `GetBalances(wallet)`, `GetConfig()` — queries + +> No `CloseAppSession()` exists on the SDK Client. Close via `SubmitAppState` with Close intent. + +## Key Reference Files + +- Protocol description: `protocol-description.md` +- Smart contract invariants: `contracts/SECURITY.md` +- Main SC entrypoint: `contracts/src/ChannelHub.sol` (design: `contracts/suggested-contract-design.md`) +- Nitronode logic: `nitronode/README.md`, `docs/legacy/` +- Advanced protocol docs: `docs/protocol/` + +## Commit Convention + +``` +feat|fix|chore|test|docs(scope): description +``` +Examples: `feat(sdk/ts): add transfer batching`, `fix(sdk-compat): export missing type` + +## Common Mistakes to Avoid + +- Don't use `ethers.js` — use `viem` for Ethereum interactions +- Don't ignore Go errors with `_` +- Don't barrel re-export classes in sdk-compat (causes SSR failures) +- Don't run `npm test && npm run build` in sdk/ts (double-tests; build already runs tests) +- Don't edit `.env` files or commit secrets +- Don't create new utilities without checking `pkg/` first (Go) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..1c3654ed1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,106 @@ +# Nitrolite + +Nitrolite is a state channel framework for Ethereum and EVM-compatible blockchains. It enables off-chain interactions (instant finality, low gas) while maintaining on-chain security guarantees. + +## Repository Structure + +| Directory | Description | Language | +|-----------|-------------|----------| +| `contracts/` | Solidity smart contracts (ChannelHub, ChannelEngine) | Solidity (Foundry) | +| `nitronode/` | Off-chain broker: ledger services, WebSocket JSON-RPC | Go | +| `sdk/ts/` | TypeScript SDK (`@yellow-org/sdk`) | TypeScript | +| `sdk/ts-compat/` | Compat layer (`@yellow-org/sdk-compat`) bridging v0.5.3 API to v1.0.0+ | TypeScript | +| `sdk/go/` | Go SDK for backend integrations | Go | +| `sdk/mcp/` | Unified MCP server — TypeScript + Go SDK context for AI agents/IDEs | TypeScript | +| `cerebro/` | Interactive CLI for channel/asset management | Go | +| `pkg/` | Shared Go packages (core, sign, rpc, app, blockchain, log) | Go | +| `docs/` | Protocol specifications, architecture docs | Markdown | +| `test/integration/` | Integration tests against a live nitronode | TypeScript | + +See stack-specific `CLAUDE.md` files in `sdk/ts/`, `sdk/ts-compat/`, and `sdk/go/` for detailed conventions. + +## Build & Test Commands + +### TypeScript SDK +```bash +cd sdk/ts && npm install # Install dependencies (first time) +cd sdk/ts && npm test # Unit tests (Jest) +cd sdk/ts && npm run build # Tests + compile (runs tests first!) +cd sdk/ts && npm run typecheck # Type check only +cd sdk/ts && npm run lint # ESLint +``` + +### TypeScript SDK Compat +```bash +cd sdk/ts-compat && npm install # Install dependencies (first time) +cd sdk/ts-compat && npm test # Unit tests (Jest) +cd sdk/ts-compat && npm run build # Compile +cd sdk/ts-compat && npm run typecheck # Type check only +``` + +### Go SDK +```bash +go test ./sdk/go/... -v # SDK tests only (from repo root) +go build ./sdk/go/... # Build SDK +go test ./... # ALL Go tests (nitronode + pkg + sdk + cerebro) +go vet ./... # Lint all Go code +``` + +### Smart Contracts +```bash +cd contracts && forge build # Compile +cd contracts && forge test # Run tests +cd contracts && forge fmt # Format +``` + +### Integration Tests +```bash +cd test/integration && npm test # Requires a running nitronode +``` + +## Important Notes + +- **Go module** is at repo root: `go.mod`, module `github.com/layer-3/nitrolite`, Go 1.25 +- **Build order**: `sdk/ts` must build before `sdk/ts-compat` (has `"@yellow-org/sdk": "file:../ts"` dependency) +- **sdk/ts build runs tests first**: `npm run build` = `npm run test && tsc`. Avoid `npm test && npm run build` (double-tests). +- **Foundry** uses git submodules for deps (`forge-std`, `openzeppelin-contracts`). Use `--recurse-submodules` on clone. +- **MCP server** (`sdk/mcp/`): run `cd sdk/mcp && npm install` before first use. +- **Never** edit `.env` files or commit secrets. + +## V1 Protocol Source of Truth + +- **API definition**: `docs/api.yaml` — canonical list of all v1 RPC methods, types, and request/response schemas +- **Protocol spec**: `docs/protocol/` — state channels, transitions, enforcement, security +- **Contract invariants**: `contracts/SECURITY.md` +- **Contract design**: `contracts/suggested-contract-design.md`, entrypoint `contracts/src/ChannelHub.sol` +- **Nitronode docs**: `nitronode/README.md`, `docs/legacy/` + +**Do NOT use `docs/legacy/API.md` as v1 reference** — it documents the 0.5.x compat-layer method names (e.g., `transfer`, `create_channel`, `auth_request`). The v1 methods use grouped names (e.g., `channels.v1.submit_state`, `app_sessions.v1.create_app_session`). + +### SDK vs Compat + +- **`@yellow-org/sdk`** (`sdk/ts/`) — v1 protocol SDK. Use for all new code. +- **`@yellow-org/sdk-compat`** (`sdk/ts-compat/`) — bridges 0.5.x API surface to v1 runtime. Wraps `Client` with `NitroliteClient`, exposes legacy types and method names. For migration only. +- **`sdk/go/`** — Go v1 SDK. No compat layer exists for Go. + +## Commit Convention + +```text +feat|fix|chore|test|docs(scope): description + +# Examples: +feat(sdk/ts): add transfer batching support +fix(sdk-compat): export missing generateRequestId +chore(contracts): update OpenZeppelin to v5.2 +test(integration): add channel resize test +``` + +## CI Workflows + +| Workflow | Trigger | What it tests | +|----------|---------|---------------| +| `test-go.yml` | PR / push | Go tests (`go test ./...`) | +| `test-forge.yml` | PR / push | Contract tests (`forge test`) | +| `test-sdk.yml` | push | TypeScript SDK tests | +| `test-integration.yml` | push | Integration tests | +| `publish-sdk.yml` | release | Publish SDK to npm | diff --git a/MIGRATION-NITRONODE.md b/MIGRATION-NITRONODE.md new file mode 100644 index 000000000..e36551237 --- /dev/null +++ b/MIGRATION-NITRONODE.md @@ -0,0 +1,61 @@ +# Clearnode → Nitronode Rename + +`clearnode` was renamed to `nitronode` starting with the release that ships +this document. Releases up to and including **v1.2.0** were published as +`clearnode`; **v1.3.0 and later** ship as `nitronode`. + +The protocol wire format and on-chain contracts are unaffected — this is a +naming change only. The rename does not break compatibility for clients +already authenticated against an existing node, but operators must update +deployments, image references and environment variables. + +## What changed + +| Surface | Before (≤ v1.2.0) | After (≥ v1.3.0) | +|---------|-------------------|------------------| +| Repository directory | `clearnode/` | `nitronode/` | +| Go binary | `clearnode` | `nitronode` | +| Go import path | `github.com/layer-3/nitrolite/clearnode/...` | `github.com/layer-3/nitrolite/nitronode/...` | +| Docker image | `ghcr.io/layer-3/nitrolite/clearnode:*` | `ghcr.io/layer-3/nitrolite/nitronode:*` | +| Helm chart name | `clearnode` | `nitronode` | +| Helm release / namespace | `clearnode` / `clearnode-` | `nitronode` / `nitronode-` | +| Env var prefix | `CLEARNODE_*` | `NITRONODE_*` | +| Prometheus metric prefix | `clearnode_*` | `nitronode_*` | +| Default sandbox WebSocket URL | `wss://clearnode-sandbox.yellow.org/v1/ws` | `wss://nitronode-sandbox.yellow.org/v1/ws` | +| Connection error message | `failed to connect to clearnode: …` | `failed to connect to nitronode: …` | + +## Backwards compatibility + +* **Env vars** — `nitronode` accepts both prefixes. Any `CLEARNODE_*` variable + is automatically mapped to its `NITRONODE_*` counterpart at startup with a + deprecation warning. The legacy prefix will be removed in a future release. +* **Cerebro config dir** — if a `clearnode-cli` directory already exists in + the user config path, it is reused with a warning. +* **Old Docker images** — pre-rename tags remain published under + `ghcr.io/layer-3/nitrolite/clearnode`. New tags are published under + `nitronode` only; there is no dual-publish window. +* **Prometheus metrics** — there is no automatic alias. Dashboards and alert + rules that reference `clearnode_*` must be updated to `nitronode_*`. + +## Steps for operators + +1. Pull the new image: `ghcr.io/layer-3/nitrolite/nitronode:`. +2. Update Helm release / namespace names if you manage them yourself. +3. Rename `CLEARNODE_*` env vars to `NITRONODE_*` (the legacy names still + work for now but emit warnings). +4. Update Prometheus alert rules and Grafana dashboards: replace + `clearnode_*` metric names with `nitronode_*`. +5. Update any pinned Docker image references in CI / deployment manifests. +6. Update DNS / SDK clients to use `nitronode-sandbox.yellow.org` (the + legacy host remains live for v1.2.0 and earlier). + +## Steps for SDK users + +The Go SDK and TypeScript SDK keep the same package paths +(`github.com/layer-3/nitrolite/sdk/go`, `@yellow-org/sdk`) and the same +exported API. Required updates: + +* Default WebSocket URL constants now point at `nitronode-sandbox.yellow.org`. +* Error message strings change from `failed to connect to clearnode` to + `failed to connect to nitronode`. Callers asserting on the old text must + update their tests. diff --git a/README.md b/README.md index 627a21c2b..25aa26a98 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ Nitrolite is a lightweight, efficient state channel framework for Ethereum and o Nitrolite is a complete state channel infrastructure consisting of several main components: 1. **Smart Contracts**: On-chain infrastructure for state channel management (ChannelHub, ChannelEngine). -2. **Clearnode**: A broker providing ledger services and off-chain state management. +2. **Nitronode** (formerly Clearnode): A broker providing ledger services and off-chain state management. 3. **Go SDK**: Comprehensive Go library for building backend and CLI applications. 4. **TypeScript SDK**: Client-side library for building web and mobile applications. -5. **Cerebro (CLI)**: Interactive command-line interface for managing channels and assets. +5. **Cerebro**: REPL for managing channels and assets. ### Key Benefits @@ -31,10 +31,10 @@ Nitrolite is a complete state channel infrastructure consisting of several main This repository contains: - **[`/contracts`](/contracts)**: Solidity smart contracts for the state channel framework. -- **[`/clearnode`](/clearnode)**: Implementation of the Clearnode service. +- **[`/nitronode`](/nitronode)**: Implementation of the Nitronode service (formerly `/clearnode`). - **[`/sdk/go`](/sdk/go)**: Official Go SDK. - **[`/sdk/ts`](/sdk/ts)**: Official TypeScript SDK. -- **[`/cerebro`](/cerebro)**: Clearnode CLI tool. +- **[`/cerebro`](/cerebro)**: Cerebro REPL. - **[`/pkg`](/pkg)**: Shared Go packages (core state machine, signing, etc.). - **[`/docs`](/docs)**: Protocol specifications and architectural documentation. @@ -66,9 +66,14 @@ For the most up-to-date contract addresses on all supported networks, see the [c See the [contract README](/contracts/README.md) for detailed contract documentation. -## Clearnode +## Nitronode -Clearnode is a message broker and state manager that enables efficient off-chain payment channels. +> **Renamed in v1.3.0** — `clearnode` was renamed to `nitronode`. Old Docker images +> (`ghcr.io/layer-3/nitrolite/clearnode`), the `clearnode-sandbox.yellow.org` +> domain, and `CLEARNODE_*` env vars are kept for v1.2.0 and earlier; new releases +> ship as `nitronode`. See [`MIGRATION-NITRONODE.md`](/MIGRATION-NITRONODE.md). + +Nitronode is a message broker and state manager that enables efficient off-chain payment channels. ### Features @@ -78,7 +83,7 @@ Clearnode is a message broker and state manager that enables efficient off-chain - **Flexible Storage**: Support for SQLite (embedded) and PostgreSQL. - **Quorum-Based Signatures**: Weight-based quorum requirements for state updates. -See the [Clearnode Documentation](/clearnode/README.md) for more details. +See the [Nitronode Documentation](/nitronode/README.md) for more details. ## SDKs @@ -100,32 +105,17 @@ npm install @layer-3/nitrolite ``` See [SDK TS README](/sdk/ts/README.md). -## Cerebro (CLI) +## Cerebro -Interactive CLI for interacting with Clearnode. +REPL for interacting with Nitronode. ```bash # From the root directory -go build -o nitrolite-cli ./cerebro -./nitrolite-cli wss://node.example.com/ws +go build -o cerebro ./cerebro +./cerebro wss://node.example.com/ws ``` See [Cerebro README](/cerebro/README.md). -## Quick Start with Docker Compose - -Get started quickly with the local development environment: - -```bash -# Start the environment -docker-compose up -d - -# This will: -# 1. Start a local Anvil blockchain on port 8545 -# 2. Deploy core Nitrolite contracts -# 3. Seed test tokens and configuration -# 4. Start the Clearnode service. -``` - ## Development ```bash diff --git a/cerebro/README.md b/cerebro/README.md index e1f2622ac..d76fc0da1 100644 --- a/cerebro/README.md +++ b/cerebro/README.md @@ -1,126 +1,145 @@ -# Clearnode CLI +# Cerebro -Command-line interface for the Clearnode Go SDK. Provides interactive access to both high-level smart operations and low-level RPC methods. +REPL for the Nitrolite Go SDK. Provides access to both high-level smart operations and low-level RPC methods. ## Installation ```bash -cd examples/cli -go build -o clearnode-cli +# From the repository root +go build -o cerebro ./cerebro ``` Or install directly: ```bash -go install github.com/layer-3/nitrolite/sdk/go/examples/cli@latest +go install github.com/layer-3/nitrolite/cerebro@latest ``` ## Quick Start ```bash -# Connect to node -./clearnode-cli wss://clearnode.example.com/ws +# Connect to node (a wallet is auto-generated on first run) +./cerebro wss://nitronode.example.com/ws -# Configure wallet -clearnode> import wallet +# Import an existing wallet (or use the auto-generated one) +cerebro> config wallet import -# Configure RPC endpoint -clearnode> import rpc 80002 https://polygon-amoy.g.alchemy.com/v2/YOUR_KEY +# Configure RPC endpoint for on-chain operations +cerebro> config rpc import 80002 https://polygon-amoy.g.alchemy.com/v2/YOUR_KEY # Verify configuration -clearnode> config +cerebro> config -# Deposit funds -clearnode> deposit 80002 usdc 100 - -# Transfer funds -clearnode> transfer 0xRecipient... usdc 50 +# Approve token spending, deposit, transfer +cerebro> approve 80002 usdc 1000 +cerebro> deposit 80002 usdc 100 +cerebro> transfer 0xRecipient... usdc 50 # Withdraw funds -clearnode> withdraw 80002 usdc 25 +cerebro> withdraw 80002 usdc 25 ``` ## Commands ### Configuration -``` -help Display command reference -config Display current configuration -wallet Display wallet address -import wallet Configure wallet (import or generate) -import rpc Configure RPC endpoint -exit Exit the CLI +```text +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 nitronode 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 ``` -### High-Level Operations - -``` -deposit Deposit to channel -withdraw Withdraw from channel -transfer Transfer to another wallet +### Operations + +```text +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 +transfer Transfer to another wallet +acknowledge Acknowledge transfer or channel creation +close-channel Close home channel on-chain +checkpoint Submit latest state on-chain ``` -### Node Information - -``` -ping Test node connection -node info Get node configuration -chains List supported blockchains -assets [chain_id] List supported assets +### Queries + +```text +ping Test node connection +chains List supported blockchains +assets [chain_id] List supported assets (optionally filter by chain) +balances [wallet] Get user balances (defaults to configured wallet) +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 ``` -### User Queries +### App Registry -``` -balances [wallet] Get user balances -transactions [wallet] Get transaction history +```text +app-info Show application details +my-apps List your registered applications +register-app [no-approval] Register a new application +app-sessions List app sessions ``` -### State Management +### Security Token Operations -``` -state [wallet] Get latest state -home-channel [wallet] Get home channel -escrow-channel Get escrow channel by ID -``` - -### App Sessions - -``` -app-sessions List app sessions +```text +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 ``` -### Session Key Management +### Other -``` -generate-session-key Generate a new session keypair -create-channel-session-key Register channel session key -channel-session-keys List active channel session keys -create-app-session-key [app_ids] [session_ids] Register app session key -app-session-keys List active app session keys +```text +help Display help message +exit Exit the CLI ``` ## Configuration Storage Default configuration locations: -- Linux: `~/.config/clearnode-cli/config.db` -- macOS: `~/Library/Application Support/clearnode-cli/config.db` -- Windows: `%APPDATA%\clearnode-cli\config.db` +- Linux: `~/.config/cerebro/config.db` +- macOS: `~/Library/Application Support/cerebro/config.db` +- Windows: `%APPDATA%\cerebro\config.db` + +If a legacy `clearnode-cli` directory exists, it will be used with a warning suggesting you rename it to `cerebro`. Override with environment variable: ```bash -export CLEARNODE_CLI_CONFIG_DIR=/custom/path +export NITRONODE_CLI_CONFIG_DIR=/custom/path ``` ## Wallet Setup -When running `import wallet`, choose: +On first run, a new wallet is automatically generated. You can also: -1. **Import existing** - Enter your private key (with or without 0x prefix) -2. **Generate new** - Create new wallet with random key +1. **Import existing** - `config wallet import` to enter your private key +2. **Generate new** - `config wallet generate` to create a new wallet +3. **Export** - `config wallet export ` to save your private key to a file WARNING: Save generated private keys immediately. They cannot be recovered. @@ -142,14 +161,6 @@ Full Ethereum addresses starting with 0x. Commands default to configured wallet Decimal amounts (e.g., 100, 0.5, 1000.25). -### Session Key Addresses - -Full Ethereum addresses (0x-prefixed) of the session key to delegate signing authority to. Generate one with `generate-session-key`. - -### Expiration Hours - -Integer number of hours until a session key expires (e.g., 24, 48, 168). - ### Comma-Separated Lists Assets, application IDs, and session IDs are passed as comma-separated values without spaces (e.g., `usdc,weth` or `app1,app2`). @@ -160,103 +171,74 @@ Assets, application IDs, and session IDs are passed as comma-separated values wi - **Command history** - Use arrow keys to navigate history - **Context-aware** - Chain IDs and assets autocomplete based on node data -## Error Messages - -Errors are prefixed by type: - -- `ERROR:` - Command failed, check parameters -- `WARNING:` - Non-critical issue -- `INFO:` - Informational message - ## Examples ### Initial Setup ```bash -./clearnode-cli wss://testnet.clearnode.example.com/ws -clearnode> import wallet -clearnode> import rpc 80002 https://polygon-amoy.g.alchemy.com/v2/KEY -clearnode> config +./cerebro wss://testnet.nitronode.example.com/ws +cerebro> config wallet import +cerebro> config rpc import 80002 https://polygon-amoy.g.alchemy.com/v2/KEY +cerebro> config ``` ### Deposit and Transfer ```bash -clearnode> deposit 80002 usdc 1000 -clearnode> balances -clearnode> transfer 0xRecipient... usdc 100 -clearnode> transactions -``` - -### Query Network - -```bash -clearnode> chains -clearnode> assets -clearnode> node info -``` - -### Inspect State - -```bash -clearnode> state usdc -clearnode> home-channel usdc -clearnode> balances 0xSomeAddress... +cerebro> approve 80002 usdc 1000 +cerebro> deposit 80002 usdc 1000 +cerebro> balances +cerebro> transfer 0xRecipient... usdc 100 +cerebro> transactions ``` ### Session Keys ```bash # Generate a new session keypair -clearnode> generate-session-key +cerebro> config session-key generate # Register a channel session key (valid for 24 hours, for usdc and weth) -clearnode> create-channel-session-key 0xSessionKeyAddr... 24 usdc,weth +cerebro> config session-key register-channel-key 0xSessionKeyAddr... 24 usdc,weth # List active channel session keys -clearnode> channel-session-keys +cerebro> config session-key channel-keys # Register an app session key (valid for 48 hours, for specific app IDs) -clearnode> create-app-session-key 0xSessionKeyAddr... 48 app1,app2 +cerebro> config session-key register-app-key 0xSessionKeyAddr... 48 app1,app2 # List active app session keys -clearnode> app-session-keys +cerebro> config session-key app-keys ``` -## Security - -- Private keys stored locally in SQLite (unencrypted) -- Database protected by OS file permissions -- Never commit config database to version control -- Backup private keys securely -- Use hardware wallets for production - -## Troubleshooting +### Query Network -**ERROR: No wallet configured** ```bash -clearnode> import wallet +cerebro> chains +cerebro> assets +cerebro> config node ``` -**ERROR: No RPC configured for chain X** +### Inspect State + ```bash -clearnode> import rpc +cerebro> state usdc +cerebro> home-channel usdc +cerebro> balances 0xSomeAddress... ``` -**Connection issues** -- Verify WebSocket URL -- Check network connectivity -- Confirm node is accessible +## Security -**Insufficient balance** -- Verify wallet has sufficient tokens -- Check token approval for contract -- Ensure gas funds available +- Private keys stored locally in SQLite (unencrypted) +- Database protected by OS file permissions +- Never commit config database to version control +- Backup private keys securely +- Use hardware wallets for production ## Architecture -``` -cli/ +```text +cerebro/ ├── main.go Entry point and terminal setup ├── operator.go Command routing and completion ├── commands.go Command implementations @@ -268,6 +250,25 @@ Uses layered architecture: - Base Client for low-level RPC access - Local SQLite for secure configuration storage +## Known issues + +- **Scrollback loss on macOS Terminal.app / iTerm2.** Cerebro uses + `github.com/c-bata/go-prompt` for the REPL. Its completion-menu renderer + reserves screen rows via `\x1bD` (Index) + `\x1bM` (Reverse Index). On + xterm-strict terminals RI pulls scrolled-out content back from the + scrollback buffer; on macOS Terminal.app and iTerm2 (default settings) RI + inserts a blank line at the top instead, permanently dropping whatever was + scrolled off. Any cerebro session that triggers a completion menu + (including typing `exit`) wipes shell history that existed before cerebro + started. Tracked upstream as + [c-bata/go-prompt#206](https://github.com/c-bata/go-prompt/issues/206) and + unresolved since 2020. + + Planned mitigation: replace `c-bata/go-prompt` with `chzyer/readline` or + `peterh/liner`. Both preserve scrollback while still providing Tab + completion; the completion glue in `operator.go` needs to be rewritten + against the new lib's hook surface. + ## License -Part of the Nitrolite project. +Part of the Nitrolite project. Licensed under the MIT License. diff --git a/cerebro/commands.go b/cerebro/commands.go index a3b15fbdf..3b811f747 100644 --- a/cerebro/commands.go +++ b/cerebro/commands.go @@ -35,7 +35,7 @@ func readSecure() string { func (o *Operator) showHelp() { fmt.Println(` -Clearnode CLI - SDK Development Tool +Cerebro - Nitrolite SDK Development Tool ===================================== CONFIGURATION @@ -46,7 +46,7 @@ CONFIGURATION 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-ws-url Set nitronode 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 @@ -636,6 +636,10 @@ func (o *Operator) getHomeChannel(ctx context.Context, wallet, asset string) { fmt.Printf("ERROR: Failed to get home channel: %v\n", err) return } + if channel == nil { + fmt.Printf("No home channel found for %s (%s)\n", wallet, asset) + return + } typeStr := "unknown" switch channel.Type { @@ -675,6 +679,10 @@ func (o *Operator) getEscrowChannel(ctx context.Context, escrowChannelID string) fmt.Printf("ERROR: Failed to get escrow channel: %v\n", err) return } + if channel == nil { + fmt.Printf("No escrow channel found with ID %s\n", escrowChannelID) + return + } typeStr := "unknown" switch channel.Type { @@ -834,6 +842,10 @@ func (o *Operator) getLatestState(ctx context.Context, wallet, asset string) { fmt.Printf("ERROR: Failed to get state: %v\n", err) return } + if state == nil { + fmt.Printf("No state found for %s (%s)\n", wallet, asset) + return + } fmt.Printf("Latest State for %s (%s)\n", wallet, asset) fmt.Println("====================================") @@ -934,7 +946,7 @@ func (o *Operator) storeSessionKey(privateKeyHex string) { fmt.Println("SUCCESS: Session key stored locally") fmt.Printf(" Address: %s\n", address) fmt.Println() - fmt.Println("Next step: Register it on the clearnode with:") + fmt.Println("Next step: Register it on the nitronode with:") fmt.Printf(" config session-key register-channel-key %s \n", address) } @@ -956,7 +968,7 @@ func (o *Operator) showSessionKey() { fmt.Println("Session Key Configuration") fmt.Println("=========================") fmt.Printf("Address: %s\n", signer.PublicKey().Address().String()) - fmt.Println("Status: Stored locally (not yet registered on clearnode)") + fmt.Println("Status: Stored locally (not yet registered on nitronode)") fmt.Println() fmt.Println("Next step: Register it with:") fmt.Printf(" config session-key register-channel-key %s \n", signer.PublicKey().Address().String()) @@ -1010,13 +1022,45 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr, return } - // Determine version by fetching existing keys + // Determine version by fetching existing keys. include_inactive=true so an expired + // prior version still surfaces — otherwise rotation would restart from version 1 and + // collide with the monotonic pointer enforced server-side. + includeInactive := true var version uint64 = 1 existingStates, err := o.client.GetLastChannelKeyStates(ctx, wallet, &sdk.GetLastChannelKeyStatesOptions{ - SessionKey: &sessionKeyAddr, + SessionKey: &sessionKeyAddr, + IncludeInactive: &includeInactive, }) if err == nil && len(existingStates) > 0 { - version = existingStates[0].Version + 1 + for _, s := range existingStates { + if s.Version >= version { + version = s.Version + 1 + } + } + } + + // SessionKeySig requires the session-key private key. Fetch it up-front and bail if the + // stored key doesn't match the address being registered — without the private key, the + // nitronode rejects the submit. + storedPK, pkErr := o.store.GetSessionKeyPrivateKey() + if pkErr != nil { + fmt.Printf("ERROR: Cannot register session key without the matching private key: %v\n", pkErr) + return + } + storedRawSigner, sigErr := sign.NewEthereumRawSigner(storedPK) + if sigErr != nil { + fmt.Printf("ERROR: Failed to load stored session key: %v\n", sigErr) + return + } + if !strings.EqualFold(storedRawSigner.PublicKey().Address().String(), sessionKeyAddr) { + fmt.Printf("ERROR: Stored session key %s does not match the address being registered (%s)\n", + storedRawSigner.PublicKey().Address().String(), sessionKeyAddr) + return + } + sessionKeySigner, sigErr := sign.NewEthereumMsgSignerFromRaw(storedRawSigner) + if sigErr != nil { + fmt.Printf("ERROR: Failed to construct session-key message signer: %v\n", sigErr) + return } expiresAt := time.Now().Add(time.Duration(expiresHours) * time.Hour) @@ -1037,6 +1081,13 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr, } state.UserSig = sig + keySig, err := sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner) + if err != nil { + fmt.Printf("ERROR: Failed to sign session key ownership: %v\n", err) + return + } + state.SessionKeySig = keySig + fmt.Println("Submitting channel session key state...") if err := o.client.SubmitChannelSessionKeyState(ctx, state); err != nil { fmt.Printf("ERROR: Failed to submit session key state: %v\n", err) @@ -1049,21 +1100,7 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr, fmt.Printf(" Assets: %s\n", strings.Join(assets, ", ")) fmt.Printf(" Expires At: %s\n", expiresAt.Format("2006-01-02 15:04:05")) - // If we have a stored session key matching this address, activate it as the state signer - storedPK, pkErr := o.store.GetSessionKeyPrivateKey() - if pkErr != nil { - return - } - storedSigner, sigErr := sign.NewEthereumRawSigner(storedPK) - if sigErr != nil { - return - } - if !strings.EqualFold(storedSigner.PublicKey().Address().String(), sessionKeyAddr) { - return - } - - // Compute metadata hash and store full session key data - metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(version, assets, expiresAt.Unix()) + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(wallet, version, assets, expiresAt.Unix()) if err != nil { fmt.Printf("WARNING: Failed to compute metadata hash: %v\n", err) return @@ -1135,10 +1172,14 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi return } - // Determine version by fetching existing keys + // Determine version by fetching existing keys. include_inactive=true so an expired + // prior version still surfaces — otherwise rotation would restart from version 1 and + // collide with the monotonic pointer enforced server-side. + includeInactive := true var version uint64 = 1 existingStates, err := o.client.GetLastAppKeyStates(ctx, wallet, &sdk.GetLastKeyStatesOptions{ - SessionKey: &sessionKeyAddr, + SessionKey: &sessionKeyAddr, + IncludeInactive: &includeInactive, }) if err == nil && len(existingStates) > 0 { for _, s := range existingStates { @@ -1148,6 +1189,28 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi } } + // SessionKeySig requires the session-key private key. + storedPK, pkErr := o.store.GetSessionKeyPrivateKey() + if pkErr != nil { + fmt.Printf("ERROR: Cannot register session key without the matching private key: %v\n", pkErr) + return + } + storedRawSigner, sigErr := sign.NewEthereumRawSigner(storedPK) + if sigErr != nil { + fmt.Printf("ERROR: Failed to load stored session key: %v\n", sigErr) + return + } + if !strings.EqualFold(storedRawSigner.PublicKey().Address().String(), sessionKeyAddr) { + fmt.Printf("ERROR: Stored session key %s does not match the address being registered (%s)\n", + storedRawSigner.PublicKey().Address().String(), sessionKeyAddr) + return + } + sessionKeySigner, sigErr := sign.NewEthereumMsgSignerFromRaw(storedRawSigner) + if sigErr != nil { + fmt.Printf("ERROR: Failed to construct session-key message signer: %v\n", sigErr) + return + } + state := app.AppSessionKeyStateV1{ UserAddress: wallet, SessionKey: sessionKeyAddr, @@ -1165,6 +1228,13 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi } state.UserSig = sig + keySig, err := sdk.SignAppSessionKeyOwnership(state, sessionKeySigner) + if err != nil { + fmt.Printf("ERROR: Failed to sign session key ownership: %v\n", err) + return + } + state.SessionKeySig = keySig + fmt.Println("Submitting app session key state...") if err := o.client.SubmitAppSessionKeyState(ctx, state); err != nil { fmt.Printf("ERROR: Failed to submit session key state: %v\n", err) diff --git a/cerebro/main.go b/cerebro/main.go index 03b7c0a74..1b5ea8aff 100644 --- a/cerebro/main.go +++ b/cerebro/main.go @@ -5,27 +5,40 @@ import ( "log" "os" "os/exec" + "os/signal" "path/filepath" + "strings" + "syscall" "github.com/c-bata/go-prompt" "golang.org/x/term" ) func main() { - const defaultWSURL = "wss://clearnode-sandbox.yellow.org/v1/ws" + const defaultWSURL = "wss://nitronode-sandbox.yellow.org/v1/ws" log.SetFlags(0) - log.SetPrefix("clearnode-cli: ") + log.SetPrefix("cerebro: ") log.SetOutput(os.Stderr) - // Get config directory - configDir := os.Getenv("CLEARNODE_CLI_CONFIG_DIR") + // Resolve config directory: explicit env > legacy "clearnode-cli" > "cerebro" + configDir := os.Getenv("NITRONODE_CLI_CONFIG_DIR") if configDir == "" { userConfDir, err := os.UserConfigDir() if err != nil { log.Fatalf("failed to get user config directory: %v", err) } - configDir = filepath.Join(userConfDir, "clearnode-cli") + + legacyDir := filepath.Join(userConfDir, "clearnode-cli") + newDir := filepath.Join(userConfDir, "cerebro") + + if info, err := os.Stat(legacyDir); err == nil && info.IsDir() { + configDir = legacyDir + fmt.Printf("WARNING: Using legacy config directory %s\n", legacyDir) + fmt.Printf(" Please rename it to %s\n", newDir) + } else { + configDir = newDir + } } if err := os.MkdirAll(configDir, 0755); err != nil { @@ -55,24 +68,68 @@ func main() { log.Fatalf("failed to create operator: %v", err) } - fmt.Println("Clearnode CLI - SDK Development Tool") + fmt.Println("Cerebro - Nitrolite SDK Development Tool") fmt.Printf("Connected to: %s\n", wsURL) fmt.Printf("Config directory: %s\n", configDir) fmt.Println("\nType 'help' for available commands or 'exit' to quit") - // Terminal handling - initialState, _ := term.GetState(int(os.Stdin.Fd())) + // Terminal handling. go-prompt switches the tty into raw mode and emits + // bracketed-paste / alternate-screen / mouse-tracking escapes. Restoring + // the termios is not enough — the terminal also needs the matching + // disable sequences or the next program in the same shell tab inherits + // broken paste behaviour and an unresponsive Ctrl-C. + initialState, stateErr := term.GetState(int(os.Stdin.Fd())) + if stateErr != nil { + log.Printf("warning: failed to capture initial terminal state: %v", stateErr) + } handleExit := func() { - term.Restore(int(os.Stdin.Fd()), initialState) - exec.Command("stty", "sane").Run() + if stateErr == nil && initialState != nil { + if err := term.Restore(int(os.Stdin.Fd()), initialState); err != nil { + log.Printf("warning: failed to restore terminal state: %v", err) + } + } + if err := exec.Command("stty", "sane").Run(); err != nil { + log.Printf("warning: failed to run 'stty sane': %v", err) + } + // Disable bracketed paste, show cursor, leave alt screen, mouse off, + // then wipe any leftover completion-menu / ghost-prompt rows that + // go-prompt leaves on the screen during its teardown. + fmt.Fprint(os.Stdout, "\x1b[?2004l\x1b[?25h\x1b[?1049l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l") + // go-prompt leaves on the screen: the redrawn ghost prompt line + // (rendered between Executor return and ExitChecker firing) plus up + // to 6 rows reserved for the completion menu (default maxSuggestion + // = 6 in go-prompt v0.2.6). Cursor is positioned somewhere below + // them. Step cursor up enough rows to land at or above the ghost + // prompt, then erase from there down. Over-erasing into the user's + // "cerebro> exit" line is acceptable — the farewell printed below + // preserves the meaning. + fmt.Fprint(os.Stdout, "\x1b[7A\r\x1b[0J") } + // Catch SIGINT / SIGTERM / SIGHUP so abnormal termination still restores + // the terminal. The in-prompt Ctrl-C keybind below handles the normal + // "exit by Ctrl-C" path; this handler covers shell-level kills. + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + go func() { + <-sigCh + handleExit() + os.Exit(130) + }() + options := append(getStyleOptions(), - prompt.OptionPrefix("clearnode> "), + prompt.OptionPrefix("cerebro> "), + // Make go-prompt exit its Run() loop when the user types "exit" so + // its own tty teardown runs before main prints anything below the + // prompt. Without this, exit racing with the prompt redraw leaves + // completion suggestions splattered over the final output. + prompt.OptionSetExitCheckerOnInput(func(in string, breakline bool) bool { + return breakline && strings.TrimSpace(in) == "exit" + }), prompt.OptionAddKeyBind(prompt.KeyBind{ Key: prompt.ControlC, Fn: func(_ *prompt.Buffer) { - log.Println("exiting Clearnode CLI") + log.Println("exiting Cerebro") handleExit() os.Exit(0) }, @@ -95,20 +152,24 @@ func main() { close(promptExitCh) }() + var farewell string select { case <-operator.Wait(): - log.Println("connection closed.") + farewell = "connection closed." case <-promptExitCh: - log.Println("session ended.") + farewell = "session ended." } + // Restore the terminal first so the farewell prints below any leftover + // prompt artefacts in normal mode, not interleaved with go-prompt's + // final redraw. handleExit() - log.Println("exiting...") + log.Println(farewell) } func getStyleOptions() []prompt.Option { return []prompt.Option{ - prompt.OptionTitle("Clearnode CLI"), + prompt.OptionTitle("Cerebro"), prompt.OptionPrefixTextColor(prompt.Green), prompt.OptionPreviewSuggestionTextColor(prompt.Blue), @@ -123,7 +184,5 @@ func getStyleOptions() []prompt.Option { prompt.OptionSelectedDescriptionTextColor(prompt.White), prompt.OptionSelectedDescriptionBGColor(prompt.DarkBlue), - - prompt.OptionShowCompletionAtStart(), } } diff --git a/cerebro/operator.go b/cerebro/operator.go index 8b2a08835..e08ac0f6f 100644 --- a/cerebro/operator.go +++ b/cerebro/operator.go @@ -261,7 +261,7 @@ func (o *Operator) complete(d prompt.Document) []prompt.Suggest { } case "node": return []prompt.Suggest{ - {Text: "set-ws-url", Description: "Set clearnode WebSocket URL"}, + {Text: "set-ws-url", Description: "Set nitronode WebSocket URL"}, {Text: "set-home-blockchain", Description: "Set home blockchain for channels"}, } case "session-key": @@ -714,8 +714,12 @@ func (o *Operator) Execute(s string) { } case "exit": - fmt.Println("Exiting...") - close(o.exitCh) + // Actual exit is driven by go-prompt's ExitChecker on "exit" input + // (see cerebro/main.go). Closing exitCh here would race the prompt + // teardown; printing here would print before go-prompt redraws the + // next prompt line, leaving a stray "cerebro> " between the message + // and the farewell. Defer the farewell to main once the prompt has + // torn itself down. default: fmt.Printf("ERROR: Unknown command: %s (type 'help' for available commands)\n", args[0]) } diff --git a/cerebro/operator_test.go b/cerebro/operator_test.go index 53e338d3e..4d138f4c2 100644 --- a/cerebro/operator_test.go +++ b/cerebro/operator_test.go @@ -154,5 +154,5 @@ func TestOperator_Connect_Failure(t *testing.T) { // Attempt to connect to a non-existent server _, err = NewOperator("ws://localhost:12345/nonexistent", t.TempDir(), s) assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to connect to clearnode") + assert.Contains(t, err.Error(), "failed to connect to nitronode") } diff --git a/clearnode/api/app_session_v1/get_last_key_states.go b/clearnode/api/app_session_v1/get_last_key_states.go deleted file mode 100644 index 83976a1c5..000000000 --- a/clearnode/api/app_session_v1/get_last_key_states.go +++ /dev/null @@ -1,59 +0,0 @@ -package app_session_v1 - -import ( - "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. -func (h *Handler) GetLastKeyStates(c *rpc.Context) { - ctx := c.Context - logger := log.FromContext(ctx) - - var req rpc.AppSessionsV1GetLastKeyStatesRequest - if err := c.Request.Payload.Translate(&req); err != nil { - c.Fail(err, "failed to parse parameters") - return - } - - if req.UserAddress == "" { - c.Fail(rpc.Errorf("wallet is required"), "") - return - } - - logger.Debug("retrieving session key states", - "wallet", req.UserAddress, - "sessionKey", req.SessionKey) - - var states []app.AppSessionKeyStateV1 - - err := h.useStoreInTx(func(tx Store) error { - var err error - states, err = tx.GetLastAppSessionKeyStates(req.UserAddress, req.SessionKey) - return err - }) - - if err != nil { - logger.Error("failed to retrieve session key states", "error", err) - c.Fail(err, "failed to retrieve session key states") - return - } - - rpcStates := make([]rpc.AppSessionKeyStateV1, len(states)) - for i, state := range states { - rpcStates[i] = mapSessionKeyStateV1(&state) - } - - resp := rpc.AppSessionsV1GetLastKeyStatesResponse{ - States: rpcStates, - } - - 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/app_session_v1/submit_deposit_state_test.go b/clearnode/api/app_session_v1/submit_deposit_state_test.go deleted file mode 100644 index 539667ae9..000000000 --- a/clearnode/api/app_session_v1/submit_deposit_state_test.go +++ /dev/null @@ -1,534 +0,0 @@ -package app_session_v1 - -import ( - "context" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "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) { - // Setup - mockStore := new(MockStore) - mockSigner := NewMockSigner() - nodeAddress := mockSigner.PublicKey().Address().String() - mockAssetStore := new(MockAssetStore) - mockStatePacker := new(MockStatePacker) - - handler := &Handler{ - assetStore: mockAssetStore, - actionGateway: &MockActionGateway{}, - stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), - statePacker: mockStatePacker, - useStoreInTx: func(handler StoreTxHandler) error { - return handler(mockStore) - }, - signer: mockSigner, - nodeAddress: nodeAddress, - metrics: metrics.NewNoopRuntimeMetricExporter(), - maxParticipants: 32, - maxSessionData: 1024, - maxSessionKeyIDs: 256, - maxSignedUpdates: 16, - } - - // Test data - create one key for both app session and channel state signing - userRawSigner := NewMockSigner() - channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) - appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) - participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) - participant2 := "0x2222222222222222222222222222222222222222" - asset := "USDC" - homeChannelID := "0xHomeChannel123" - depositAmount := decimal.NewFromInt(100) - appSessionID := "0xAppSession123" - - // Create existing app session - existingAppSession := &app.AppSessionV1{ - SessionID: appSessionID, - ApplicationID: "test-app", - Participants: []app.AppParticipantV1{ - { - WalletAddress: participant1, - SignatureWeight: 1, - }, - { - WalletAddress: participant2, - SignatureWeight: 1, - }, - }, - Quorum: 1, - Nonce: 12345, - Status: app.AppSessionStatusOpen, - Version: 1, - SessionData: "", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - // Create user's current state (before deposit) - currentUserState := core.State{ - ID: core.GetStateID(participant1, asset, 1, 1), - Transition: core.Transition{ - Type: core.TransitionTypeVoid, - }, - Asset: asset, - UserWallet: participant1, - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - HomeLedger: core.Ledger{ - TokenAddress: "0xTokenAddress", - BlockchainID: 1, - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.NewFromInt(500), - NodeBalance: decimal.NewFromInt(0), - NodeNetFlow: decimal.NewFromInt(0), - }, - EscrowLedger: nil, - UserSig: nil, - NodeSig: nil, - } - - // Create incoming user state (with commit transition) - incomingUserState := currentUserState.NextState() - - _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) - require.NoError(t, err) - - // Sign the incoming user state with channel wallet signer (adds 0x01 prefix) - mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) - packedUserState, _ := mockStatePacker.PackState(*incomingUserState) - userSig, _ := channelWalletSigner.Sign(packedUserState) - userSigStr := userSig.String() - incomingUserState.UserSig = &userSigStr - - // Create app state update and sign with app wallet signer (includes 0xA1 prefix for verifyQuorum) - appStateUpdateCore := app.AppStateUpdateV1{ - AppSessionID: appSessionID, - Intent: app.AppStateUpdateIntentDeposit, - Version: 2, - Allocations: []app.AppAllocationV1{ - { - Participant: participant1, - Asset: asset, - Amount: depositAmount, - }, - }, - SessionData: `{"updated": "data"}`, - } - packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) - appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) - appSigHex := hexutil.Encode(appSigBytes) - - appStateUpdate := rpc.AppStateUpdateV1{ - AppSessionID: appSessionID, - Intent: app.AppStateUpdateIntentDeposit, - Version: "2", // Next version - Allocations: []rpc.AppAllocationV1{ - { - Participant: participant1, - Asset: asset, - Amount: depositAmount.String(), - }, - }, - SessionData: `{"updated": "data"}`, - } - - // Mock expectations - mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Once() - mockStore.On("CheckOpenChannel", participant1, asset).Return("0x03", true, nil).Once() - 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 - mockStore.On("GetParticipantAllocations", appSessionID).Return( - map[string]map[string]decimal.Decimal{}, - nil, - ).Once() - - // Mock ledger entry recording - mockStore.On("RecordLedgerEntry", participant1, appSessionID, asset, depositAmount).Return(nil).Once() - - // Mock app session update - mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { - return session.SessionID == appSessionID && - session.Version == 2 && - session.SessionData == `{"updated": "data"}` - })).Return(nil).Once() - - // Mock user state storage - mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { - return state.UserWallet == participant1 && - state.Version == incomingUserState.Version && - state.Transition.Type == core.TransitionTypeCommit && - state.NodeSig != nil - })).Return(nil).Once() - - // Mock transaction recording - mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { - return tx.TxType == core.TransactionTypeCommit && - tx.Amount.Equal(depositAmount) && - tx.ToAccount == appSessionID - })).Return(nil).Once() - - // Create RPC request - rpcState := toRPCState(*incomingUserState) - reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ - AppStateUpdate: appStateUpdate, - QuorumSigs: []string{appSigHex}, - UserState: rpcState, - } - - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) - - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), - } - - // Execute - handler.SubmitDepositState(ctx) - - // Assert - assert.NotNil(t, ctx.Response) - - // Check for errors first - if respErr := ctx.Response.Error(); respErr != nil { - t.Fatalf("Unexpected error response: %v", respErr) - } - - assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) - - // Parse response - var response rpc.AppSessionsV1SubmitDepositStateResponse - err = ctx.Response.Payload.Translate(&response) - require.NoError(t, err) - assert.NotEmpty(t, response.StateNodeSig, "Node signature should be present") - - // Verify all mock expectations - mockStore.AssertExpectations(t) -} - -func TestSubmitDepositState_InvalidTransitionType(t *testing.T) { - // Setup - mockStore := new(MockStore) - mockSigner := NewMockSigner() - nodeAddress := mockSigner.PublicKey().Address().String() - mockAssetStore := new(MockAssetStore) - mockStatePacker := new(MockStatePacker) - - handler := &Handler{ - assetStore: mockAssetStore, - actionGateway: &MockActionGateway{}, - stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), - statePacker: mockStatePacker, - useStoreInTx: func(handler StoreTxHandler) error { - return handler(mockStore) - }, - signer: mockSigner, - nodeAddress: nodeAddress, - metrics: metrics.NewNoopRuntimeMetricExporter(), - maxParticipants: 32, - maxSessionData: 1024, - maxSessionKeyIDs: 256, - maxSignedUpdates: 16, - } - - // Test data - participant1 := "0x1111111111111111111111111111111111111111" - asset := "USDC" - homeChannelID := "0xHomeChannel123" - appSessionID := "0xAppSession123" - - // Create user state with WRONG transition type (transfer_send instead of commit) - userState := core.State{ - ID: core.GetStateID(participant1, asset, 1, 2), - Asset: asset, - UserWallet: participant1, - Epoch: 1, - Version: 2, - Transition: core.Transition{ - Type: core.TransitionTypeTransferSend, // Wrong type! - TxID: "tx-id", - AccountID: appSessionID, - Amount: decimal.NewFromInt(100), - }, - HomeChannelID: &homeChannelID, - HomeLedger: core.Ledger{ - TokenAddress: "0xTokenAddress", - BlockchainID: 1, - UserBalance: decimal.NewFromInt(400), - UserNetFlow: decimal.NewFromInt(500), - NodeBalance: decimal.NewFromInt(0), - NodeNetFlow: decimal.NewFromInt(-100), - }, - } - - // Sign the user state - userKey, _ := crypto.GenerateKey() - mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) - packedUserState, _ := mockStatePacker.PackState(userState) - userSigBytes, _ := crypto.Sign(crypto.Keccak256Hash(packedUserState).Bytes(), userKey) - userSigHex := hexutil.Encode(userSigBytes) - userState.UserSig = &userSigHex - - // Create app state update with proper hex signature (though we'll fail before signature check) - appSigKey, _ := crypto.GenerateKey() - depositAmt := decimal.NewFromInt(100) - appStateUpdateCore := app.AppStateUpdateV1{ - AppSessionID: appSessionID, - Intent: app.AppStateUpdateIntentDeposit, - Version: 2, - Allocations: []app.AppAllocationV1{ - { - Participant: participant1, - Asset: asset, - Amount: depositAmt, - }, - }, - SessionData: "", - } - packedAppStateUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) - appSigBytes, _ := crypto.Sign(crypto.Keccak256Hash(packedAppStateUpdate).Bytes(), appSigKey) - appSigHex := hexutil.Encode(appSigBytes) - - appStateUpdate := rpc.AppStateUpdateV1{ - AppSessionID: appSessionID, - Intent: app.AppStateUpdateIntentDeposit, - Version: "2", - Allocations: []rpc.AppAllocationV1{ - { - Participant: participant1, - Asset: asset, - Amount: "100", - }, - }, - } - - // 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) - reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ - AppStateUpdate: appStateUpdate, - QuorumSigs: []string{appSigHex}, - UserState: rpcState, - } - - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) - - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), - } - - // Execute - handler.SubmitDepositState(ctx) - - // Assert - assert.NotNil(t, ctx.Response) - - // Verify response contains error - err = ctx.Response.Error() - require.Error(t, err) - assert.Contains(t, err.Error(), "commit") - - // Verify no mocks were called since we fail early - mockStore.AssertExpectations(t) -} - -func TestSubmitDepositState_QuorumNotMet(t *testing.T) { - // Setup - mockStore := new(MockStore) - mockSigner := NewMockSigner() - nodeAddress := mockSigner.PublicKey().Address().String() - mockAssetStore := new(MockAssetStore) - mockStatePacker := new(MockStatePacker) - - handler := &Handler{ - assetStore: mockAssetStore, - actionGateway: &MockActionGateway{}, - stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), - statePacker: mockStatePacker, - useStoreInTx: func(handler StoreTxHandler) error { - return handler(mockStore) - }, - signer: mockSigner, - nodeAddress: nodeAddress, - metrics: metrics.NewNoopRuntimeMetricExporter(), - maxParticipants: 32, - maxSessionData: 1024, - maxSessionKeyIDs: 256, - maxSignedUpdates: 16, - } - - // Test data - create one key for both app session and channel state signing - userRawSigner := NewMockSigner() - channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) - appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) - participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) - participant2 := "0x2222222222222222222222222222222222222222" - asset := "USDC" - homeChannelID := "0xHomeChannel123" - depositAmount := decimal.NewFromInt(100) - appSessionID := "0xAppSession123" - - // Create existing app session with higher quorum requirement - existingAppSession := &app.AppSessionV1{ - SessionID: appSessionID, - ApplicationID: "test-app", - Participants: []app.AppParticipantV1{ - { - WalletAddress: participant1, - SignatureWeight: 1, - }, - { - WalletAddress: participant2, - SignatureWeight: 1, - }, - }, - Quorum: 2, // Need both signatures - Nonce: 12345, - Status: app.AppSessionStatusOpen, - Version: 1, - } - - // Create user state - currentUserState := core.State{ - ID: core.GetStateID(participant1, asset, 1, 1), - Transition: core.Transition{ - Type: core.TransitionTypeVoid, - }, - Asset: asset, - UserWallet: participant1, - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - HomeLedger: core.Ledger{ - TokenAddress: "0xTokenAddress", - BlockchainID: 1, - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.NewFromInt(500), - NodeBalance: decimal.NewFromInt(0), - NodeNetFlow: decimal.NewFromInt(0), - }, - } - - incomingUserState := currentUserState.NextState() - - _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) - require.NoError(t, err) - - mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) - packedUserState, _ := mockStatePacker.PackState(*incomingUserState) - userSig, _ := channelWalletSigner.Sign(packedUserState) - userSigStr := userSig.String() - incomingUserState.UserSig = &userSigStr - - // Create app state update and sign with app wallet signer (includes 0xA1 prefix for verifyQuorum) - appStateUpdateCore := app.AppStateUpdateV1{ - AppSessionID: appSessionID, - Intent: app.AppStateUpdateIntentDeposit, - Version: 2, - Allocations: []app.AppAllocationV1{ - { - Participant: participant1, - Asset: asset, - Amount: depositAmount, - }, - }, - SessionData: "", - } - packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) - appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) - appSigHex := hexutil.Encode(appSigBytes) - - appStateUpdate := rpc.AppStateUpdateV1{ - AppSessionID: appSessionID, - Intent: app.AppStateUpdateIntentDeposit, - Version: "2", - Allocations: []rpc.AppAllocationV1{ - { - Participant: participant1, - Asset: asset, - Amount: depositAmount.String(), - }, - }, - } - - // Mock expectations - mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Once() - mockStore.On("CheckOpenChannel", participant1, asset).Return("0x03", true, nil).Once() - 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 - rpcState := toRPCState(*incomingUserState) - reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ - AppStateUpdate: appStateUpdate, - QuorumSigs: []string{appSigHex}, - UserState: rpcState, - } - - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) - - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), - } - - // Execute - handler.SubmitDepositState(ctx) - - // Assert - assert.NotNil(t, ctx.Response) - - // Verify response contains error about quorum - err = ctx.Response.Error() - require.Error(t, err) - assert.Contains(t, err.Error(), "quorum not met") - - // Verify all mocks were called - mockStore.AssertExpectations(t) -} diff --git a/clearnode/api/app_session_v1/submit_session_key_state.go b/clearnode/api/app_session_v1/submit_session_key_state.go deleted file mode 100644 index c92970a93..000000000 --- a/clearnode/api/app_session_v1/submit_session_key_state.go +++ /dev/null @@ -1,137 +0,0 @@ -package app_session_v1 - -import ( - "strings" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - - "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. -func (h *Handler) SubmitSessionKeyState(c *rpc.Context) { - ctx := c.Context - logger := log.FromContext(ctx) - - var reqPayload rpc.AppSessionsV1SubmitSessionKeyStateRequest - if err := c.Request.Payload.Translate(&reqPayload); err != nil { - c.Fail(err, "failed to parse parameters") - return - } - - if len(reqPayload.State.ApplicationIDs) > h.maxSessionKeyIDs { - c.Fail(rpc.Errorf("application_ids array exceeds maximum length of %d", h.maxSessionKeyIDs), "") - return - } - if len(reqPayload.State.AppSessionIDs) > h.maxSessionKeyIDs { - c.Fail(rpc.Errorf("app_session_ids array exceeds maximum length of %d", h.maxSessionKeyIDs), "") - return - } - - logger.Debug("processing session key state submission", - "userAddress", reqPayload.State.UserAddress, - "sessionKey", reqPayload.State.SessionKey, - "version", reqPayload.State.Version) - - // Convert RPC type to core type - coreState, err := unmapSessionKeyStateV1(&reqPayload.State) - if err != nil { - c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") - return - } - - // Validate required fields - if !common.IsHexAddress(coreState.UserAddress) { - c.Fail(rpc.Errorf("invalid_session_key_state: invalid user_address"), "") - return - } - if !common.IsHexAddress(coreState.SessionKey) { - c.Fail(rpc.Errorf("invalid_session_key_state: invalid session_key"), "") - return - } - if coreState.Version == 0 { - c.Fail(rpc.Errorf("invalid_session_key_state: version must be greater than 0"), "") - return - } - if coreState.ExpiresAt.Before(time.Now()) { - c.Fail(rpc.Errorf("invalid_session_key_state: expires_at must be in the future"), "") - return - } - if coreState.UserSig == "" { - c.Fail(rpc.Errorf("invalid_session_key_state: user_sig is required"), "") - return - } - - // Pack the session key state for signature verification (ABI encoding) - packedState, err := app.PackAppSessionKeyStateV1(coreState) - if err != nil { - c.Fail(rpc.Errorf("invalid_session_key_state: failed to pack state: %v", err), "") - return - } - - // Decode the user signature - sigBytes, err := hexutil.Decode(coreState.UserSig) - if err != nil { - c.Fail(rpc.Errorf("invalid_session_key_state: failed to decode user_sig: %v", err), "") - return - } - - // Recover signer address from signature using ECDSA recovery - ethMsgRecoverer, err := sign.NewSigValidator(sign.TypeEthereumMsg) - if err != nil { - c.Fail(rpc.Errorf("internal_error: failed to create signature validator: %v", err), "") - return - } - - recoveredAddress, err := ethMsgRecoverer.Recover(packedState, sigBytes) - if err != nil { - c.Fail(rpc.Errorf("invalid_session_key_state: failed to recover signer: %v", err), "") - return - } - - // Verify the recovered address matches user_address - if !strings.EqualFold(recoveredAddress, coreState.UserAddress) { - c.Fail(rpc.Errorf("invalid_session_key_state: signature does not match user_address"), "") - return - } - - // Validate version and store the session key state - err = h.useStoreInTx(func(tx Store) error { - // Check the latest version for this (user_address, session_key) pair; 0 means no state exists - latestVersion, err := tx.GetLastAppSessionKeyVersion(coreState.UserAddress, coreState.SessionKey) - if err != nil { - return rpc.Errorf("failed to check existing session key state: %v", err) - } - - if coreState.Version != latestVersion+1 { - return rpc.Errorf("invalid_session_key_state: expected version %d, got %d", latestVersion+1, coreState.Version) - } - - return tx.StoreAppSessionKeyState(coreState) - }) - - if err != nil { - logger.Error("failed to store session key state", "error", err) - c.Fail(err, "failed to store session key state") - return - } - - resp := rpc.AppSessionsV1SubmitSessionKeyStateResponse{} - - payload, err := rpc.NewPayload(resp) - if err != nil { - c.Fail(err, "failed to create response") - return - } - - c.Succeed(c.Request.Method, payload) - logger.Info("successfully stored session key state", - "userAddress", coreState.UserAddress, - "sessionKey", coreState.SessionKey, - "version", coreState.Version) -} diff --git a/clearnode/api/channel_v1/get_last_key_states.go b/clearnode/api/channel_v1/get_last_key_states.go deleted file mode 100644 index 001d96008..000000000 --- a/clearnode/api/channel_v1/get_last_key_states.go +++ /dev/null @@ -1,59 +0,0 @@ -package channel_v1 - -import ( - "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. -func (h *Handler) GetLastKeyStates(c *rpc.Context) { - ctx := c.Context - logger := log.FromContext(ctx) - - var req rpc.ChannelsV1GetLastKeyStatesRequest - if err := c.Request.Payload.Translate(&req); err != nil { - c.Fail(err, "failed to parse parameters") - return - } - - if req.UserAddress == "" { - c.Fail(rpc.Errorf("user_address is required"), "") - return - } - - logger.Debug("retrieving channel session key states", - "userAddress", req.UserAddress, - "sessionKey", req.SessionKey) - - var states []core.ChannelSessionKeyStateV1 - - err := h.useStoreInTx(func(tx Store) error { - var err error - states, err = tx.GetLastChannelSessionKeyStates(req.UserAddress, req.SessionKey) - return err - }) - - if err != nil { - logger.Error("failed to retrieve channel session key states", "error", err) - c.Fail(err, "failed to retrieve channel session key states") - return - } - - rpcStates := make([]rpc.ChannelSessionKeyStateV1, len(states)) - for i, state := range states { - rpcStates[i] = mapChannelSessionKeyStateV1(&state) - } - - resp := rpc.ChannelsV1GetLastKeyStatesResponse{ - States: rpcStates, - } - - 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/channel_v1/request_creation_test.go b/clearnode/api/channel_v1/request_creation_test.go deleted file mode 100644 index 1db939081..000000000 --- a/clearnode/api/channel_v1/request_creation_test.go +++ /dev/null @@ -1,415 +0,0 @@ -package channel_v1 - -import ( - "context" - "strconv" - "testing" - - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "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) { - // Setup - mockTxStore := new(MockStore) - mockMemoryStore := new(MockMemoryStore) - mockAssetStore := new(MockAssetStore) - mockSigner := NewMockSigner() - nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) - nodeAddress := mockSigner.PublicKey().Address().String() - minChallenge := uint32(3600) // 1 hour - mockStatePacker := new(MockStatePacker) - - handler := &Handler{ - stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), - statePacker: mockStatePacker, - useStoreInTx: func(handler StoreTxHandler) error { - err := handler(mockTxStore) - if err != nil { - return err - } - return nil - }, - memoryStore: mockMemoryStore, - nodeSigner: nodeSigner, - nodeAddress: nodeAddress, - minChallenge: minChallenge, - metrics: metrics.NewNoopRuntimeMetricExporter(), - maxSessionKeyIDs: 256, - actionGateway: &MockActionGateway{}, - } - - // Test data - derive userWallet from a user signer key - userSigner := NewMockSigner() - userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) - userWallet := userSigner.PublicKey().Address().String() - asset := "USDC" - tokenAddress := "0xTokenAddress" - blockchainID := uint64(1) - nonce := uint64(12345) - challenge := uint32(86400) - depositAmount := decimal.NewFromInt(1000) - - // Create void state (starting point) - voidState := core.NewVoidState(asset, userWallet) - - // Create next state from void - initialState := voidState.NextState() - - channelDef := core.ChannelDefinition{ - Nonce: nonce, - Challenge: challenge, - ApprovedSigValidators: "0x03", - } - _, err := initialState.ApplyChannelCreation(channelDef, blockchainID, tokenAddress, nodeAddress) - require.NoError(t, err) - - // Apply the home deposit transition to update balances - _, err = initialState.ApplyHomeDepositTransition(depositAmount) - require.NoError(t, err) - - // Set up mock for PackState (called during signing) - mockAssetStore.On("GetTokenDecimals", blockchainID, tokenAddress).Return(uint8(6), nil) - - // Sign the initial state with user's wallet signer (adds 0x01 prefix) - packedState, err := core.PackState(*initialState, mockAssetStore) - require.NoError(t, err) - userSig, err := userWalletSigner.Sign(packedState) - require.NoError(t, err) - userSigStr := userSig.String() - initialState.UserSig = &userSigStr - - // Mock expectations for handler - mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() - mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil).Once() - mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() - mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil).Once() - mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) - mockTxStore.On("CreateChannel", mock.MatchedBy(func(channel core.Channel) bool { - return channel.UserWallet == userWallet && - channel.Type == core.ChannelTypeHome && - channel.BlockchainID == blockchainID && - channel.TokenAddress == tokenAddress && - channel.Nonce == nonce && - channel.ChallengeDuration == challenge && - channel.Status == core.ChannelStatusVoid && - channel.StateVersion == 0 - })).Return(nil).Once() - mockTxStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { - // For home_deposit: fromAccount is homeChannelID, toAccount is userWallet - return tx.TxType == core.TransactionTypeHomeDeposit && - tx.ToAccount == userWallet && - tx.FromAccount != "" // homeChannelID will be set by handler - })).Return(nil).Once() - mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { - return state.UserWallet == userWallet && - state.Asset == asset && - state.Version == 1 && - state.Epoch == 0 && - state.NodeSig != nil && - state.HomeChannelID != nil - })).Return(nil).Once() - - // Create RPC request - rpcState := toRPCState(*initialState) - reqPayload := rpc.ChannelsV1RequestCreationRequest{ - State: rpcState, - ChannelDefinition: rpc.ChannelDefinitionV1{ - Nonce: strconv.FormatUint(nonce, 10), - Challenge: challenge, - ApprovedSigValidators: "0x03", - }, - } - - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) - - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.Message{ - RequestID: 1, - Method: rpc.ChannelsV1RequestCreationMethod.String(), - Payload: payload, - }, - } - - // Execute - handler.RequestCreation(ctx) - - // Assert - assert.NotNil(t, ctx.Response) - - // Check for errors first - if respErr := ctx.Response.Error(); respErr != nil { - t.Fatalf("Unexpected error response: %v", respErr) - } - - assert.Equal(t, rpc.ChannelsV1RequestCreationMethod.String(), ctx.Response.Method) - assert.NotNil(t, ctx.Response.Payload) - - // Verify response contains signature - var response rpc.ChannelsV1RequestCreationResponse - err = ctx.Response.Payload.Translate(&response) - require.NoError(t, err) - assert.NotEmpty(t, response.Signature) - - // Verify all mocks were called - mockMemoryStore.AssertExpectations(t) - mockAssetStore.AssertExpectations(t) - mockTxStore.AssertExpectations(t) -} - -func TestRequestCreation_Acknowledgement_Success(t *testing.T) { - // Setup - mockTxStore := new(MockStore) - mockMemoryStore := new(MockMemoryStore) - mockAssetStore := new(MockAssetStore) - mockSigner := NewMockSigner() - nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) - nodeAddress := mockSigner.PublicKey().Address().String() - minChallenge := uint32(3600) // 1 hour - mockStatePacker := new(MockStatePacker) - - handler := &Handler{ - stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), - statePacker: mockStatePacker, - useStoreInTx: func(handler StoreTxHandler) error { - err := handler(mockTxStore) - if err != nil { - return err - } - return nil - }, - memoryStore: mockMemoryStore, - nodeSigner: nodeSigner, - nodeAddress: nodeAddress, - minChallenge: minChallenge, - metrics: metrics.NewNoopRuntimeMetricExporter(), - maxSessionKeyIDs: 256, - actionGateway: &MockActionGateway{}, - } - - // Test data - derive userWallet from a user signer key - userSigner := NewMockSigner() - userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) - userWallet := userSigner.PublicKey().Address().String() - asset := "USDC" - tokenAddress := "0xTokenAddress" - blockchainID := uint64(1) - nonce := uint64(12345) - challenge := uint32(86400) - - // Create void state (starting point) - voidState := core.NewVoidState(asset, userWallet) - - // Create next state from void - initialState := voidState.NextState() - - channelDef := core.ChannelDefinition{ - Nonce: nonce, - Challenge: challenge, - ApprovedSigValidators: "0x03", - } - _, err := initialState.ApplyChannelCreation(channelDef, blockchainID, tokenAddress, nodeAddress) - require.NoError(t, err) - - // Apply acknowledgement transition (channel creation with no deposit) - _, err = initialState.ApplyAcknowledgementTransition() - require.NoError(t, err) - - // Set up mock for PackState (called during signing) - mockAssetStore.On("GetTokenDecimals", blockchainID, tokenAddress).Return(uint8(6), nil) - - // Sign the initial state with user's wallet signer (adds 0x01 prefix) - packedState, err := core.PackState(*initialState, mockAssetStore) - require.NoError(t, err) - userSig, err := userWalletSigner.Sign(packedState) - require.NoError(t, err) - userSigStr := userSig.String() - initialState.UserSig = &userSigStr - - // Mock expectations for handler - mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() - mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil).Once() - mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() - mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil).Once() - mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) - mockTxStore.On("CreateChannel", mock.MatchedBy(func(channel core.Channel) bool { - return channel.UserWallet == userWallet && - channel.Type == core.ChannelTypeHome && - channel.BlockchainID == blockchainID && - channel.TokenAddress == tokenAddress && - channel.Nonce == nonce && - channel.ChallengeDuration == challenge && - channel.Status == core.ChannelStatusVoid && - channel.StateVersion == 0 - })).Return(nil).Once() - // For acknowledgement: no RecordTransaction call expected, only StoreUserState - mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { - return state.UserWallet == userWallet && - state.Asset == asset && - state.Version == 1 && - state.Epoch == 0 && - state.NodeSig != nil && - state.HomeChannelID != nil && - state.Transition.Type == core.TransitionTypeAcknowledgement - })).Return(nil).Once() - - // Create RPC request - rpcState := toRPCState(*initialState) - reqPayload := rpc.ChannelsV1RequestCreationRequest{ - State: rpcState, - ChannelDefinition: rpc.ChannelDefinitionV1{ - Nonce: strconv.FormatUint(nonce, 10), - Challenge: challenge, - ApprovedSigValidators: "0x03", - }, - } - - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) - - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.Message{ - RequestID: 1, - Method: rpc.ChannelsV1RequestCreationMethod.String(), - Payload: payload, - }, - } - - // Execute - handler.RequestCreation(ctx) - - // Assert - assert.NotNil(t, ctx.Response) - - // Check for errors first - if respErr := ctx.Response.Error(); respErr != nil { - t.Fatalf("Unexpected error response: %v", respErr) - } - - assert.Equal(t, rpc.ChannelsV1RequestCreationMethod.String(), ctx.Response.Method) - assert.NotNil(t, ctx.Response.Payload) - - // Verify response contains signature - var response rpc.ChannelsV1RequestCreationResponse - err = ctx.Response.Payload.Translate(&response) - require.NoError(t, err) - assert.NotEmpty(t, response.Signature) - - // Verify all mocks were called - notably RecordTransaction should NOT have been called - mockMemoryStore.AssertExpectations(t) - mockAssetStore.AssertExpectations(t) - mockTxStore.AssertExpectations(t) - mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything) -} - -func TestRequestCreation_InvalidChallenge(t *testing.T) { - // Setup - mockTxStore := new(MockStore) - mockMemoryStore := new(MockMemoryStore) - mockAssetStore := new(MockAssetStore) - mockSigner := NewMockSigner() - nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) - nodeAddress := mockSigner.PublicKey().Address().String() - minChallenge := uint32(3600) // 1 hour - mockStatePacker := new(MockStatePacker) - - handler := &Handler{ - stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), - statePacker: mockStatePacker, - useStoreInTx: func(handler StoreTxHandler) error { - return handler(mockTxStore) - }, - memoryStore: mockMemoryStore, - nodeSigner: nodeSigner, - nodeAddress: nodeAddress, - minChallenge: minChallenge, - metrics: metrics.NewNoopRuntimeMetricExporter(), - maxSessionKeyIDs: 256, - actionGateway: &MockActionGateway{}, - } - - // Test data - userWallet := "0x1234567890123456789012345678901234567890" - asset := "USDC" - tokenAddress := "0xToken" - nonce := uint64(12345) - lowChallenge := uint32(1800) // 30 minutes - below minimum - - // Calculate home channel ID - homeChannelID, err := core.GetHomeChannelID( - nodeAddress, - userWallet, - asset, - nonce, - lowChallenge, - "0x03", - ) - require.NoError(t, err) - - mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, uint64(1)).Return(true, nil).Once() - mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() - mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil).Once() - - // Create RPC request with challenge below minimum - reqPayload := rpc.ChannelsV1RequestCreationRequest{ - State: rpc.StateV1{ - ID: core.GetStateID(userWallet, asset, 1, 1), - UserWallet: userWallet, - Asset: asset, - Epoch: "1", - Version: "1", - HomeChannelID: &homeChannelID, - Transition: rpc.TransitionV1{ - Amount: "0", - }, - HomeLedger: rpc.LedgerV1{ - TokenAddress: tokenAddress, - BlockchainID: "1", - UserBalance: "0", - UserNetFlow: "0", - NodeBalance: "0", - NodeNetFlow: "0", - }, - }, - ChannelDefinition: rpc.ChannelDefinitionV1{ - Nonce: strconv.FormatUint(nonce, 10), - Challenge: lowChallenge, - ApprovedSigValidators: "0x03", - }, - } - - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) - - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.Message{ - RequestID: 1, - Method: rpc.ChannelsV1RequestCreationMethod.String(), - Payload: payload, - }, - } - - // Execute - handler.RequestCreation(ctx) - - // Assert - assert.NotNil(t, ctx.Response) - - // Verify response contains error - err = ctx.Response.Error() - require.Error(t, err) - assert.Contains(t, err.Error(), "challenge") - - // Verify all mocks were called - mockTxStore.AssertExpectations(t) -} diff --git a/clearnode/api/channel_v1/submit_session_key_state.go b/clearnode/api/channel_v1/submit_session_key_state.go deleted file mode 100644 index 3f464d367..000000000 --- a/clearnode/api/channel_v1/submit_session_key_state.go +++ /dev/null @@ -1,98 +0,0 @@ -package channel_v1 - -import ( - "time" - - "github.com/ethereum/go-ethereum/common" - - "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. -func (h *Handler) SubmitSessionKeyState(c *rpc.Context) { - ctx := c.Context - logger := log.FromContext(ctx) - - var reqPayload rpc.ChannelsV1SubmitSessionKeyStateRequest - if err := c.Request.Payload.Translate(&reqPayload); err != nil { - c.Fail(err, "failed to parse parameters") - return - } - - logger.Debug("processing channel session key state submission", - "userAddress", reqPayload.State.UserAddress, - "sessionKey", reqPayload.State.SessionKey, - "version", reqPayload.State.Version) - - // Convert RPC type to core type - coreState, err := unmapChannelSessionKeyStateV1(&reqPayload.State) - if err != nil { - c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") - return - } - - // Validate required fields - if !common.IsHexAddress(coreState.UserAddress) { - c.Fail(rpc.Errorf("invalid_session_key_state: invalid user_address"), "") - return - } - if !common.IsHexAddress(coreState.SessionKey) { - c.Fail(rpc.Errorf("invalid_session_key_state: invalid session_key"), "") - return - } - if coreState.Version == 0 { - c.Fail(rpc.Errorf("invalid_session_key_state: version must be greater than 0"), "") - return - } - if coreState.ExpiresAt.Before(time.Now()) { - c.Fail(rpc.Errorf("invalid_session_key_state: expires_at must be in the future"), "") - return - } - if coreState.UserSig == "" { - c.Fail(rpc.Errorf("invalid_session_key_state: user_sig is required"), "") - return - } - - // Validate user's signature over the session key state - if err := core.ValidateChannelSessionKeyAuthSigV1(coreState); err != nil { - c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") - return - } - - // Validate version and store the session key state - err = h.useStoreInTx(func(tx Store) error { - // Check the latest version for this (user_address, session_key) pair; 0 means no state exists - latestVersion, err := tx.GetLastChannelSessionKeyVersion(coreState.UserAddress, coreState.SessionKey) - if err != nil { - return rpc.Errorf("failed to check existing session key state: %v", err) - } - - if coreState.Version != latestVersion+1 { - return rpc.Errorf("invalid_session_key_state: expected version %d, got %d", latestVersion+1, coreState.Version) - } - - return tx.StoreChannelSessionKeyState(coreState) - }) - - if err != nil { - logger.Error("failed to store channel session key state", "error", err) - c.Fail(err, "failed to store channel session key state") - return - } - - resp := rpc.ChannelsV1SubmitSessionKeyStateResponse{} - - payload, err := rpc.NewPayload(resp) - if err != nil { - c.Fail(err, "failed to create response") - return - } - - c.Succeed(c.Request.Method, payload) - logger.Info("successfully stored channel session key state", - "userAddress", coreState.UserAddress, - "sessionKey", coreState.SessionKey, - "version", coreState.Version) -} diff --git a/clearnode/api/rate_limits.go b/clearnode/api/rate_limits.go deleted file mode 100644 index 55041e2c4..000000000 --- a/clearnode/api/rate_limits.go +++ /dev/null @@ -1,51 +0,0 @@ -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/utils.go b/clearnode/api/utils.go deleted file mode 100644 index dfbc54469..000000000 --- a/clearnode/api/utils.go +++ /dev/null @@ -1,28 +0,0 @@ -package api - -import "github.com/layer-3/nitrolite/pkg/rpc" - -func getMethodPath(c *rpc.Context) string { - switch c.Request.Method { - case rpc.AppSessionsV1SubmitAppStateMethod.String(): - var reqPayload rpc.AppSessionsV1SubmitAppStateRequest - if err := c.Request.Payload.Translate(&reqPayload); err != nil { - break - } - return reqPayload.AppStateUpdate.Intent.String() - case rpc.ChannelsV1RequestCreationMethod.String(): - var reqPayload rpc.ChannelsV1RequestCreationRequest - if err := c.Request.Payload.Translate(&reqPayload); err != nil { - break - } - return reqPayload.State.Transition.Type.String() - case rpc.ChannelsV1SubmitStateMethod.String(): - var reqPayload rpc.ChannelsV1SubmitStateRequest - if err := c.Request.Payload.Translate(&reqPayload); err != nil { - break - } - return reqPayload.State.Transition.Type.String() - } - - return "default" -} diff --git a/clearnode/chart/Chart.yaml b/clearnode/chart/Chart.yaml deleted file mode 100644 index 63103d6c8..000000000 --- a/clearnode/chart/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -apiVersion: v2 -description: Clearnode Helm chart -name: clearnode -version: 1.0.0 diff --git a/clearnode/chart/README.md b/clearnode/chart/README.md deleted file mode 100644 index 7ee043e87..000000000 --- a/clearnode/chart/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# clearnode - -![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) - -Clearnode Helm chart - -## Prerequisites - -- Kubernetes 1.24+ -- Helm 3.0+ -- For TLS: cert-manager installed in the cluster -- For Secrets Management (optional): - - [helm-secrets](https://github.com/jkroepke/helm-secrets/wiki) plugin: `helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.4` - - [vals](https://github.com/helmfile/vals): `go install github.com/helmfile/vals/cmd/vals@v0.41.0` - -## Installing the Chart - -To install the chart with the release name `my-release`: -```bash -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. - -## Uninstalling the Chart - -To uninstall/delete the `my-release` deployment: -```bash -helm delete my-release -``` - -## Parameters - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| affinity | object | `{}` | Affinity settings | -| autoscaling.enabled | bool | `false` | Enable autoscaling | -| autoscaling.maxReplicas | int | `100` | Maximum number of replicas | -| autoscaling.minReplicas | int | `2` | Minimum number of replicas | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization | -| autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target memory utilization | -| config.args | list | `["clearnode"]` | List of arguments to pass to the container | -| config.database.driver | string | `"sqlite"` | Database driver (sqlite, postgres) | -| config.database.host | string | `""` | Database host | -| config.database.name | string | `"clearnode"` | Database name | -| config.database.password | string | `"changeme"` | Database password | -| config.database.path | string | `"clearnet.db?cache=shared"` | Database path (for sqlite) | -| config.database.port | int | `5432` | Database port | -| config.database.sslmode | string | `"disable"` | Database SSL mode (disable, require, verify-ca, verify-full) | -| config.database.user | string | `"changeme"` | Database user | -| config.envSecret | string | `""` | Name of the secret containing environment variables | -| config.extraEnvs | object | `{}` | Additional environment variables as key-value pairs | -| config.logLevel | string | `"info"` | Log level (info, debug, warn, error) | -| 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/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 | -| metrics.endpoint | string | `"/metrics"` | Metrics endpoint path | -| metrics.podmonitoring.enabled | bool | `false` | Enable PodMonitoring for Managed Prometheus | -| metrics.port | int | `4242` | Metrics port | -| metrics.scrapeInterval | string | `"30s"` | Metrics scrape interval | -| networking.externalHostname | string | `"clearnode.example.com"` | External hostname for the gateway | -| networking.gateway.className | string | `"envoy-gateway"` | Gateway class name | -| networking.gateway.enabled | bool | `true` | Enable API gateway | -| networking.gateway.ipAddressName | string | `""` | GKE static IP address name (GKE only) | -| networking.ingress.annotations | object | `{}` | Ingress annotations | -| networking.ingress.className | string | `"nginx"` | Ingress class name | -| networking.ingress.enabled | bool | `false` | Enable ingress | -| networking.ingress.grpc | bool | `false` | Enable GRPC for ingress | -| networking.ingress.tls.enabled | bool | `false` | Enable TLS for ingress | -| networking.tlsClusterIssuer | string | `"zerossl-prod"` | TLS cluster issuer | -| nodeSelector | object | `{}` | Node selector | -| probes.liveness.enabled | bool | `false` | Enable liveness probe | -| probes.liveness.type | string | `"tcp"` | Liveness probe type (http, tcp) | -| probes.readiness.enabled | bool | `false` | Enable readiness probe | -| probes.readiness.type | string | `"tcp"` | Readiness probe type (http, tcp) | -| replicaCount | int | `1` | Number of replicas | -| resources.limits | object | `{}` | Resource limits | -| resources.requests | object | `{}` | Resource requests | -| service.http.enabled | bool | `true` | Enable HTTP service | -| service.http.path | string | `"/"` | HTTP service path | -| service.http.port | int | `8000` | HTTP service port | -| serviceAccount | string | `""` | Service account name | -| tolerations | list | `[]` | Tolerations | - -## Gateway Configuration - -By default, the chart creates an API Gateway and configures it to use TLS via cert-manager. To use this feature: - -1. Create a cert-manager ClusterIssuer -2. Configure `gateway.tlsClusterIssuer` with the issuer name -3. Set `gateway.externalHostname` to your domain name - -> **Warning**: The Gateway currently does not support configurations with a static IP address. Ensure that your setup uses a dynamic DNS or hostname for proper functionality. Alternatively, you can configure an ingress resource to use a static IP address if required. - -## Managing Secrets - -For managing sensitive values like API keys and credentials, you can use `helm-secrets` with `vals`: - -1. Set up the required environment variable: - ```bash - export HELM_SECRETS_BACKEND=vals - ``` - -2. Create a values file with your secrets (e.g., `secrets.yaml`) and refer to secrets using the vals syntax: - ```yaml - apiKey: ref+awssecrets://my-secret/api-key - database: - password: ref+vault://secret/data/database?key=password - ``` - -3. When deploying or upgrading, reference your secrets file with the `secrets://` prefix: - ```bash - helm upgrade --install my-release git+https://github.com/layer-3/clearnode@chart?ref=main \ - -f values.yaml \ - -f secrets://secrets.yaml - ``` - -The vals tool supports [multiple backends](https://github.com/helmfile/vals/tree/main?tab=readme-ov-file#supported-backends) including: -- AWS Secrets Manager and SSM Parameter Store -- Google Cloud Secret Manager -- HashiCorp Vault -- Azure Key Vault -- And many more - -For detailed usage, consult the [helm-secrets documentation](https://github.com/jkroepke/helm-secrets/wiki). - -## Troubleshooting - -### Common Issues - -- **Database Connection Issues**: Ensure the database connection URL is correct and the database is accessible from the cluster -- **TLS Certificate Issues**: Check cert-manager logs for problems with certificate issuance -- **Blockchain Connection Issues**: Verify RPC endpoint URLs are correct and accessible - -For more detailed debugging, check the application logs: - -```bash -kubectl logs -l app=clearnode -``` - ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) \ No newline at end of file diff --git a/clearnode/chart/README.md.gotmpl b/clearnode/chart/README.md.gotmpl deleted file mode 100644 index 352b5f66e..000000000 --- a/clearnode/chart/README.md.gotmpl +++ /dev/null @@ -1,92 +0,0 @@ -# {{ template "chart.name" . }} - -{{ template "chart.versionBadge" . }} - -{{ template "chart.description" . }} - -## Prerequisites - -- Kubernetes 1.24+ -- Helm 3.0+ -- For TLS: cert-manager installed in the cluster -- For Secrets Management (optional): - - [helm-secrets](https://github.com/jkroepke/helm-secrets/wiki) plugin: `helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.4` - - [vals](https://github.com/helmfile/vals): `go install github.com/helmfile/vals/cmd/vals@v0.41.0` - -## Installing the Chart - -To install the chart with the release name `my-release`: -```bash -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. - -## Uninstalling the Chart - -To uninstall/delete the `my-release` deployment: -```bash -helm delete my-release -``` - -## Parameters - -{{ template "chart.valuesTable" . }} - -## Gateway Configuration - -By default, the chart creates an API Gateway and configures it to use TLS via cert-manager. To use this feature: - -1. Create a cert-manager ClusterIssuer -2. Configure `gateway.tlsClusterIssuer` with the issuer name -3. Set `gateway.externalHostname` to your domain name - -> **Warning**: The Gateway currently does not support configurations with a static IP address. Ensure that your setup uses a dynamic DNS or hostname for proper functionality. Alternatively, you can configure an ingress resource to use a static IP address if required. - -## Managing Secrets - -For managing sensitive values like API keys and credentials, you can use `helm-secrets` with `vals`: - -1. Set up the required environment variable: - ```bash - export HELM_SECRETS_BACKEND=vals - ``` - -2. Create a values file with your secrets (e.g., `secrets.yaml`) and refer to secrets using the vals syntax: - ```yaml - apiKey: ref+awssecrets://my-secret/api-key - database: - password: ref+vault://secret/data/database?key=password - ``` - -3. When deploying or upgrading, reference your secrets file with the `secrets://` prefix: - ```bash - helm upgrade --install my-release git+https://github.com/layer-3/clearnode@chart?ref=main \ - -f values.yaml \ - -f secrets://secrets.yaml - ``` - -The vals tool supports [multiple backends](https://github.com/helmfile/vals/tree/main?tab=readme-ov-file#supported-backends) including: -- AWS Secrets Manager and SSM Parameter Store -- Google Cloud Secret Manager -- HashiCorp Vault -- Azure Key Vault -- And many more - -For detailed usage, consult the [helm-secrets documentation](https://github.com/jkroepke/helm-secrets/wiki). - -## Troubleshooting - -### Common Issues - -- **Database Connection Issues**: Ensure the database connection URL is correct and the database is accessible from the cluster -- **TLS Certificate Issues**: Check cert-manager logs for problems with certificate issuance -- **Blockchain Connection Issues**: Verify RPC endpoint URLs are correct and accessible - -For more detailed debugging, check the application logs: - -```bash -kubectl logs -l app=clearnode -``` - -{{ template "helm-docs.versionFooter" . }} \ No newline at end of file diff --git a/clearnode/chart/config/prod/action_gateway.yaml b/clearnode/chart/config/prod/action_gateway.yaml deleted file mode 100644 index 67ac1e8bc..000000000 --- a/clearnode/chart/config/prod/action_gateway.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# 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/assets.yaml b/clearnode/chart/config/prod/assets.yaml deleted file mode 100644 index b2082d340..000000000 --- a/clearnode/chart/config/prod/assets.yaml +++ /dev/null @@ -1,105 +0,0 @@ -assets: -# Alphabetically sorted by symbol (case insensitive) - - symbol: "beatwav" - name: "BeatWav Token" - tokens: - - blockchain_id: 137 - address: "0x45268ba6c9A0459Eda6F6fAb4E5083c61730F375" - decimals: 6 - - blockchain_id: 1 - address: "0xDB33fEC4e2994a675133320867a6439Da4A5acD8" - decimals: 18 - - symbol: "bnb" - name: "BNB" - tokens: - - blockchain_id: 56 - address: "0x0000000000000000000000000000000000000000" - decimals: 18 - - symbol: "eth" - name: "Ether" - tokens: - - blockchain_id: 8453 - address: "0x0000000000000000000000000000000000000000" - decimals: 18 - - blockchain_id: 59144 - address: "0x0000000000000000000000000000000000000000" - decimals: 18 - - symbol: "link" - name: "ChainLink Token" - tokens: - - blockchain_id: 56 - address: "0xF8A0BF9cF54Bb92F17374d9e9A321E6a111a51bD" # pegged - decimals: 18 - - symbol: "tower" - name: "TOWER Token" - tokens: - - blockchain_id: 8453 # Base Mainnet - address: "0xf7C1CEfCf7E1dd8161e00099facD3E1Db9e528ee" - decimals: 18 - - symbol: "usdc" - name: "USD Coin" - tokens: - - blockchain_id: 1 - address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" - decimals: 6 - - blockchain_id: 14 - symbol: "USDC.e" - address: "0xFbDa5F676cB37624f28265A144A48B0d6e87d3b6" - decimals: 6 - - blockchain_id: 56 - address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" # pegged - decimals: 18 - - blockchain_id: 137 - address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" - decimals: 6 - - blockchain_id: 480 - address: "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1" - decimals: 6 - - blockchain_id: 30 - address: "0x3A15461d8AE0f0Fb5fA2629e9dA7D66A794a6E37" - decimals: 18 - - blockchain_id: 747 - address: "0x2aaBea2058b5aC2D339b163C6Ab6f2b6d53aabED" - decimals: 6 - - blockchain_id: 2020 - address: "0x0b7007c13325c48911f73a2dad5fa5dcbf808adc" - decimals: 6 - - blockchain_id: 8453 - address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" - decimals: 6 - - blockchain_id: 59144 - address: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff" - decimals: 6 - - blockchain_id: 1440000 - address: "0xa16148c6Ac9EDe0D82f0c52899e22a575284f131" - decimals: 6 - - symbol: "usdt" - name: "Tether USD" - tokens: - - blockchain_id: 56 - address: "0x55d398326f99059fF775485246999027B3197955" # pegged - decimals: 18 - - blockchain_id: 8217 - address: "0xd077a400968890eacc75cdc901f0356c943e4fdb" - decimals: 6 - - blockchain_id: 8453 - address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2" - decimals: 6 - - blockchain_id: 59144 - address: "0xA219439258ca9da29E9Cc4cE5596924745e12B93" - decimals: 6 - - symbol: "weth" - name: "wETH" - tokens: - - blockchain_id: 56 - address: "0x2170Ed0880ac9A755fd29B2688956BD959F933F8" # pegged - decimals: 18 - - blockchain_id: 137 - address: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" - decimals: 18 - - symbol: "xrp" - name: "Ripple" - tokens: - - blockchain_id: 1440000 - address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - decimals: 18 diff --git a/clearnode/chart/config/prod/blockchains.yaml b/clearnode/chart/config/prod/blockchains.yaml deleted file mode 100644 index 7f75f2910..000000000 --- a/clearnode/chart/config/prod/blockchains.yaml +++ /dev/null @@ -1,54 +0,0 @@ -default_contract_addresses: - custody: "0x6F71a38d919ad713D0AfE0eB712b95064Fc2616f" - adjudicator: "0x14980dF216722f14c42CA7357b06dEa7eB408b10" - -blockchains: -- name: ethereum - id: 1 - contract_addresses: - balance_checker: "0xb1f8e55c7f64d203c1400b9d8555d050f94adf39" -- name: flare - id: 14 - disabled: true - contract_addresses: - balance_checker: "0x714698e8fe7896bb84A46c0705944c4a48580819" -- name: bsc - id: 56 - contract_addresses: - balance_checker: "0x2352c63A83f9Fd126af8676146721Fa00924d7e4" -- name: polygon - id: 137 - contract_addresses: - balance_checker: "0x2352c63A83f9Fd126af8676146721Fa00924d7e4" -- name: world_chain - id: 480 - contract_addresses: - balance_checker: "0x6D3B5EFa1f81f65037cD842F48E44BcBCa48CBEF" -- name: flow - id: 747 - disabled: true - contract_addresses: - balance_checker: "0xa3f2f64455c9f8D68d9dCAeC2605D64680FaF898" -- name: ronin - id: 2020 - disabled: true - contract_addresses: - balance_checker: "0x714698e8fe7896bb84A46c0705944c4a48580819" -- name: kaia - id: 8217 - contract_addresses: - balance_checker: "0x714698e8fe7896bb84A46c0705944c4a48580819" -- name: base - id: 8453 - contract_addresses: - custody: "0x490fb189DdE3a01B00be9BA5F41e3447FbC838b6" - adjudicator: "0x7de4A0736Cf5740fD3Ca2F2e9cc85c9AC223eF0C" - balance_checker: "0x3ba5A41eA17fd4950a641a057dC0bEb8E8ff1521" -- name: linea - id: 59144 - contract_addresses: - balance_checker: "0xF62e6a41561b3650a69Bb03199C735e3E3328c0D" -- name: xrpl_evm - id: 1440000 - contract_addresses: - balance_checker: "0x714698e8fe7896bb84A46c0705944c4a48580819" diff --git a/clearnode/chart/config/prod/clearnode.yaml b/clearnode/chart/config/prod/clearnode.yaml deleted file mode 100644 index 10a5a9f10..000000000 --- a/clearnode/chart/config/prod/clearnode.yaml +++ /dev/null @@ -1,55 +0,0 @@ -config: - args: ["clearnode"] - logLevel: info - database: - driver: postgres - host: postgresql.core - port: 5432 - name: clearnet_prod - user: clearnet_prod_admin - envSecret: "" - extraEnvs: - MSG_EXPIRY_TIME: "60" - -image: - repository: ghcr.io/layer-3/nitrolite/clearnode - tag: 0.3.1-rc.18 - -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: clearnet.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/prod/secrets.yaml b/clearnode/chart/config/prod/secrets.yaml deleted file mode 100644 index 8426a50b8..000000000 --- a/clearnode/chart/config/prod/secrets.yaml +++ /dev/null @@ -1,16 +0,0 @@ -config: - database: - password: ref+tfstategs://terraform-state-deploy/gke-uat-postgresql-admin/default.tfstate/output.postgresql_user_passwords["clearnet_prod_admin"] - secretEnvs: - BROKER_PRIVATE_KEY: ref+gcpsecrets://ynet-stage/clearnet-prod-broker-private-key?version=latest - ETHEREUM_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-eth-mainnet-blockchain-rpc?version=latest - FLARE_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-flare-blockchain-rpc?version=latest - BSC_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-bsc-blockchain-rpc?version=latest - POLYGON_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-polygon-blockchain-rpc?version=latest - WORLD_CHAIN_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-worldchain-blockchain-rpc?version=latest - FLOW_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-flow-blockchain-rpc?version=latest - RONIN_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-ronin-blockchain-rpc?version=latest - KAIA_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-kaia-blockchain-rpc?version=latest - BASE_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-base-blockchain-rpc?version=latest - LINEA_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-linea-mainnet-blockchain-rpc?version=latest - XRPL_EVM_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-prod-xrpl-evm-mainnet-blockchain-rpc?version=latest diff --git a/clearnode/chart/config/sandbox-v1/action_gateway.yaml b/clearnode/chart/config/sandbox-v1/action_gateway.yaml deleted file mode 100644 index 277b774ec..000000000 --- a/clearnode/chart/config/sandbox-v1/action_gateway.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# 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/blockchains.yaml b/clearnode/chart/config/sandbox-v1/blockchains.yaml deleted file mode 100644 index e7615d224..000000000 --- a/clearnode/chart/config/sandbox-v1/blockchains.yaml +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 5dcd61f4f..000000000 --- a/clearnode/chart/config/sandbox-v1/clearnode.yaml +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index 67ac1e8bc..000000000 --- a/clearnode/chart/config/sandbox/action_gateway.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# 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/assets.yaml b/clearnode/chart/config/sandbox/assets.yaml deleted file mode 100644 index 4ee2eb0bc..000000000 --- a/clearnode/chart/config/sandbox/assets.yaml +++ /dev/null @@ -1,26 +0,0 @@ -assets: - - name: "ytest.USD" - symbol: "ytest.usd" - tokens: - - blockchain_id: 11155111 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 59141 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 80002 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 84532 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 1449000 - disabled: true - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - name: Ether - symbol: eth - tokens: - - blockchain_id: 0 - address: "0x0000000000000000000000000000000000000000" - decimals: 18 diff --git a/clearnode/chart/config/sandbox/blockchains.yaml b/clearnode/chart/config/sandbox/blockchains.yaml deleted file mode 100644 index f12aea0de..000000000 --- a/clearnode/chart/config/sandbox/blockchains.yaml +++ /dev/null @@ -1,28 +0,0 @@ -default_contract_addresses: - custody: "0x019B65A265EB3363822f2752141b3dF16131b262" - adjudicator: "0x7c7ccbc98469190849BCC6c926307794fDfB11F2" - -blockchains: -- name: polygon_amoy - id: 80002 - contract_addresses: - balance_checker: "0x9d1E88627884e066B81A02d69BCB2437a520534C" -- name: base_sepolia - id: 84532 - contract_addresses: - balance_checker: "0x33e57a8900882B8D5A038eC3Aa844c19Acfc539A" -- name: ethereum_sepolia - id: 11155111 - contract_addresses: - balance_checker: "0x86700f6bc63a42ee645e204b361d7c0f643c111b" -- name: linea_sepolia - id: 59141 - contract_addresses: - balance_checker: "0x424A079B89571a515Fd6fe0ba614060D5Fd8E16A" - block_step: 5000 -- name: xrpl_evm_testnet - id: 1449000 - contract_addresses: - custody: "0x442f5544983eA73ED75FbDD252bc03842D185284" - adjudicator: "0xBa28DC8F50d076Df76fF9959f31A320686faa373" - balance_checker: "0x714698e8fe7896bb84A46c0705944c4a48580819" diff --git a/clearnode/chart/config/sandbox/clearnode.yaml b/clearnode/chart/config/sandbox/clearnode.yaml deleted file mode 100644 index 420ccec06..000000000 --- a/clearnode/chart/config/sandbox/clearnode.yaml +++ /dev/null @@ -1,55 +0,0 @@ -config: - args: ["clearnode"] - logLevel: info - database: - driver: postgres - host: postgresql.core - port: 5432 - name: clearnet_sandbox - user: clearnet_sandbox_admin - envSecret: "" - extraEnvs: - MSG_EXPIRY_TIME: "60" - -image: - repository: ghcr.io/layer-3/nitrolite/clearnode - tag: 6069d30 - -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: clearnet-sandbox.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/sandbox/secrets.yaml b/clearnode/chart/config/sandbox/secrets.yaml deleted file mode 100644 index 68ee2bb92..000000000 --- a/clearnode/chart/config/sandbox/secrets.yaml +++ /dev/null @@ -1,10 +0,0 @@ -config: - database: - password: ref+tfstategs://terraform-state-deploy/gke-uat-postgresql-admin/default.tfstate/output.postgresql_user_passwords["clearnet_sandbox_admin"] - secretEnvs: - BROKER_PRIVATE_KEY: ref+gcpsecrets://ynet-stage/clearnet-sandbox-broker-private-key?version=latest - POLYGON_AMOY_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-sandbox-polygon-amoy-blockchain-rpc?version=latest - BASE_SEPOLIA_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-sandbox-base-sepolia-blockchain-rpc?version=latest - ETHEREUM_SEPOLIA_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-sandbox-eth-sepolia-blockchain-rpc?version=latest - LINEA_SEPOLIA_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-sandbox-linea-sepolia-blockchain-rpc?version=latest - XRPL_EVM_TESTNET_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-sandbox-xrpl-evm-testnet-blockchain-rpc?version=latest diff --git a/clearnode/chart/config/v1-rc/action_gateway.yaml b/clearnode/chart/config/v1-rc/action_gateway.yaml deleted file mode 100644 index 67ac1e8bc..000000000 --- a/clearnode/chart/config/v1-rc/action_gateway.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# 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 deleted file mode 100644 index 80c7f299e..000000000 --- a/clearnode/chart/config/v1-rc/assets.yaml +++ /dev/null @@ -1,33 +0,0 @@ -assets: - - name: "USD Circle" - symbol: "usdc" - decimals: 6 - suggested_blockchain_id: 11155111 - tokens: - - blockchain_id: 11155111 - address: "0x6E2C4707DA119425dF2c722E2695300154652f56" - decimals: 6 - - name: "Wrapped ETH" - symbol: "weth" - decimals: 18 - suggested_blockchain_id: 11155111 - tokens: - - blockchain_id: 11155111 - address: "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5" - decimals: 18 - - name: "Ether" - symbol: "eth" - decimals: 18 - suggested_blockchain_id: 11155111 - tokens: - - 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 deleted file mode 100644 index 2c63d8745..000000000 --- a/clearnode/chart/config/v1-rc/blockchains.yaml +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index b92af869c..000000000 --- a/clearnode/chart/config/v1-rc/clearnode.yaml +++ /dev/null @@ -1,84 +0,0 @@ -config: - args: ["clearnode"] - logLevel: info - database: - driver: postgres - host: pgbouncer - port: 5432 - name: clearnode_v1_rc - user: clearnode_v1_rc_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/v1-rc-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.0.0-rc.0 - -service: - http: - enabled: true - port: 7824 - path: /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-v1-rc.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: true - pods: - - name: get-config-1 - specs: ["get-config:10000:100"] - - name: get-config-2 - specs: ["get-config:10000:100"] - - name: get-config-3 - specs: ["get-config:10000:100"] - - name: get-config-4 - specs: ["get-config:10000:100"] - - name: get-config-5 - specs: ["get-config:10000:100"] diff --git a/clearnode/chart/templates/debug-deployment.yaml b/clearnode/chart/templates/debug-deployment.yaml deleted file mode 100644 index 7fa273de8..000000000 --- a/clearnode/chart/templates/debug-deployment.yaml +++ /dev/null @@ -1,65 +0,0 @@ -{{- 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/ingress.yaml b/clearnode/chart/templates/ingress.yaml deleted file mode 100644 index 73935fda2..000000000 --- a/clearnode/chart/templates/ingress.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if and .Values.networking.ingress.enabled .Values.service.http.enabled }} -apiVersion: {{ include "clearnode.ingress.apiVersion" . }} -kind: Ingress -metadata: - name: {{ include "clearnode.common.fullname" . }} - labels: - {{- include "clearnode.common.labels" . | nindent 4 }} - annotations: - {{- include "clearnode.ingress.annotations" . | nindent 4 }} -spec: - rules: - - host: {{ .Values.networking.externalHostname }} - http: - paths: - {{- include "clearnode.ingress.httpPath" . | nindent 10 }} - {{- include "clearnode.ingress.tls" . | nindent 2 }} -{{- end }} diff --git a/clearnode/config/compose/example/.env b/clearnode/config/compose/example/.env deleted file mode 100644 index 45243578e..000000000 --- a/clearnode/config/compose/example/.env +++ /dev/null @@ -1,3 +0,0 @@ -BROKER_PRIVATE_KEY="0xac0974bec38a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" -POLYGON_AMOY_BLOCKCHAIN_RPC="wss://polygon-amoy-bor-rpc.publicnode.com" -BASE_SEPOLIA_BLOCKCHAIN_RPC="wss://base-sepolia.drpc.org" diff --git a/clearnode/config/compose/example/assets.yaml b/clearnode/config/compose/example/assets.yaml deleted file mode 100644 index ad2267c60..000000000 --- a/clearnode/config/compose/example/assets.yaml +++ /dev/null @@ -1,20 +0,0 @@ -assets: - - name: "ytest.USD" - symbol: "ytest.usd" - decimals: 6 - tokens: - - blockchain_id: 11155111 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 59141 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 80002 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 84532 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 - - blockchain_id: 1449000 - address: "0xDB9F293e3898c9E5536A3be1b0C56c89d2b32DEb" - decimals: 6 diff --git a/clearnode/config/compose/example/blockchains.yaml b/clearnode/config/compose/example/blockchains.yaml deleted file mode 100644 index 5b0c98929..000000000 --- a/clearnode/config/compose/example/blockchains.yaml +++ /dev/null @@ -1,22 +0,0 @@ -default_contract_address: "" - -blockchains: -- name: polygon_amoy - id: 80002 - contract_address: "" -- name: base_sepolia - id: 84532 - contract_address: "" -- name: ethereum_sepolia - id: 11155111 - disabled: true - contract_address: "" -- name: linea_sepolia - id: 59141 - disabled: true - contract_address: "" -- name: xrpl_evm_testnet - id: 1449000 - disabled: true - contract_address: "" - block_step: 499 diff --git a/clearnode/config/compose/integration/.env b/clearnode/config/compose/integration/.env deleted file mode 100644 index a47389d56..000000000 --- a/clearnode/config/compose/integration/.env +++ /dev/null @@ -1 +0,0 @@ -BROKER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" diff --git a/clearnode/config/compose/integration/assets.yaml b/clearnode/config/compose/integration/assets.yaml deleted file mode 100644 index 35042c769..000000000 --- a/clearnode/config/compose/integration/assets.yaml +++ /dev/null @@ -1,15 +0,0 @@ -assets: - - name: "yintegration.USD" - symbol: "yintegration.usd" - decimals: 6 - tokens: - - blockchain_id: 31337 - address: "0xbD24c53072b9693A35642412227043Ffa5fac382" - decimals: 6 - - name: "yintegration.ETH" - symbol: "yintegration.eth" - decimals: 18 - tokens: - - blockchain_id: 31337 - address: "0xAf119209932D7EDe63055E60854E81acC4063a12" - decimals: 18 diff --git a/clearnode/config/compose/integration/blockchains.yaml b/clearnode/config/compose/integration/blockchains.yaml deleted file mode 100644 index e03bad7b7..000000000 --- a/clearnode/config/compose/integration/blockchains.yaml +++ /dev/null @@ -1,6 +0,0 @@ -default_contract_address: - -blockchains: -- name: anvil - id: 31337 - contract_address: "" diff --git a/clearnode/docker-compose.yml b/clearnode/docker-compose.yml deleted file mode 100644 index ba55aad6d..000000000 --- a/clearnode/docker-compose.yml +++ /dev/null @@ -1,58 +0,0 @@ -x-pg-env: &pg-env - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - PGPASSWORD: postgres - -services: - database: - image: postgres:17.2 - ports: - - "5432:5432" - environment: - <<: *pg-env - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] - interval: 5s - retries: 5 - start_period: 10s - - db-init: - image: postgres:17.2 - environment: - <<: *pg-env - volumes: - - ./config/migrations/postgres:/migrations - - ./scripts:/scripts - command: ["bash", "/scripts/db-init.sh"] - depends_on: - database: - condition: service_healthy - - clearnode: - build: - context: ./ - dockerfile: Dockerfile - environment: - CLEARNODE_LOG_LEVEL: "info" - CLEARNODE_DATABASE_URL: postgresql://postgres:postgres@database:5432/postgres?sslmode=disable - MSG_EXPIRY_TIME: "60" - CLEARNODE_CONFIG_DIR_PATH: /config - volumes: - - ./config/compose/local:/config:ro - ports: - - "8000:8000" - - "4242:4242" - depends_on: - database: - condition: service_healthy - db-init: - condition: service_completed_successfully - - restart: unless-stopped - -volumes: - postgres_data: - driver: local diff --git a/clearnode/event_handlers/interface.go b/clearnode/event_handlers/interface.go deleted file mode 100644 index 7b4e011ac..000000000 --- a/clearnode/event_handlers/interface.go +++ /dev/null @@ -1,56 +0,0 @@ -package event_handlers - -import ( - "github.com/layer-3/nitrolite/pkg/core" - "github.com/shopspring/decimal" -) - -// 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 channel and state data. -// All methods should be implemented to work within database transactions. -// Implementations are typically provided by the database layer and wrapped by StoreTxProvider. -type Store interface { - // GetLastStateByChannelID retrieves the most recent state for a given channel. - // If signed is true, only returns states with both user and node signatures. - // Returns nil if no matching state exists. - GetLastStateByChannelID(channelID string, signed bool) (*core.State, error) - - // GetStateByChannelIDAndVersion retrieves a specific state version for a channel. - // Returns nil if the state with the specified version does not exist. - GetStateByChannelIDAndVersion(channelID string, version uint64) (*core.State, error) - - // UpdateChannel persists changes to a channel's metadata (status, version, etc). - // The channel must already exist in the database. - UpdateChannel(channel core.Channel) error - - // GetChannelByID retrieves a channel by its unique identifier. - // Returns nil if the channel does not exist. - GetChannelByID(channelID string) (*core.Channel, error) - - // ScheduleCheckpoint schedules a checkpoint operation for a home channel state. - // This queues the state to be submitted on-chain to update the channel's on-chain state. - ScheduleCheckpoint(stateID string, chainID uint64) error - - // ScheduleInitiateEscrowDeposit schedules an initiate for an escrow deposit operation. - // This queues the state to be submitted on-chain to finalize an escrow deposit. - ScheduleInitiateEscrowDeposit(stateID string, chainID uint64) error - - // ScheduleFinalizeEscrowDeposit schedules a finalize for an escrow deposit operation. - // This queues the state to be submitted on-chain to finalize an escrow deposit. - ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error - - // 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 deleted file mode 100644 index ff3dbb7b1..000000000 --- a/clearnode/event_handlers/service.go +++ /dev/null @@ -1,461 +0,0 @@ -package event_handlers - -import ( - "context" - "time" - - "github.com/layer-3/nitrolite/pkg/core" - "github.com/layer-3/nitrolite/pkg/log" -) - -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). -// All handlers execute within database transactions provided by useStoreInTx to ensure atomicity. -type EventHandlerService struct { - useStoreInTx StoreTxProvider -} - -// NewEventHandlerService creates a new EventHandlerService instance. -// The useStoreInTx parameter wraps all store operations in database transactions. -// The logger is used for structured logging of event processing. -func NewEventHandlerService(useStoreInTx StoreTxProvider, logger log.Logger) *EventHandlerService { - return &EventHandlerService{ - useStoreInTx: useStoreInTx, - } -} - -// HandleHomeChannelCreated processes the HomeChannelCreated event emitted when a home channel -// is successfully created on-chain. It updates the channel status to Open and sets the state version. -// The channel must exist in the database with type ChannelTypeHome, otherwise a warning is logged. -func (s *EventHandlerService) HandleHomeChannelCreated(ctx context.Context, event *core.HomeChannelCreatedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during HomeChannelCreated event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeHome { - logger.Warn("channel type mismatch during HomeChannelCreated event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) - return nil - } - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusOpen - - err = tx.UpdateChannel(*channel) - if err != nil { - return err - } - - logger.Info("handled HomeChannelCreated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleHomeChannelMigrated processes the HomeChannelMigrated event emitted when a home channel -// is migrated to a new version or blockchain. This is currently not implemented and logs a warning. -// TODO: Implement HomeChannelMigrated handler logic -func (s *EventHandlerService) HandleHomeChannelMigrated(ctx context.Context, event *core.HomeChannelMigratedEvent) error { - logger := log.FromContext(ctx) - logger.Warn("unexpected HomeChannelMigrated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion) - return nil -} - -// HandleHomeChannelCheckpointed processes the HomeChannelCheckpointed event emitted when a channel -// state is successfully checkpointed on-chain. It updates the channel's state version and clears -// the Challenged status if present, returning the channel to Open status. -func (s *EventHandlerService) HandleHomeChannelCheckpointed(ctx context.Context, event *core.HomeChannelCheckpointedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during HomeChannelCheckpointed event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeHome { - logger.Warn("channel type mismatch during HomeChannelCheckpointed event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) - return nil - } - channel.StateVersion = event.StateVersion - - if channel.Status == core.ChannelStatusChallenged { - channel.Status = core.ChannelStatusOpen - } - - err = tx.UpdateChannel(*channel) - if err != nil { - return err - } - - logger.Info("handled HomeChannelCheckpointed event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleHomeChannelChallenged processes the HomeChannelChallenged event emitted when a potentially -// stale state is submitted on-chain. It updates the channel status to Challenged, sets the challenge -// expiration time, and automatically schedules a checkpoint of the latest signed state if available -// to resolve the challenge. -func (s *EventHandlerService) HandleHomeChannelChallenged(ctx context.Context, event *core.HomeChannelChallengedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during HomeChannelChallenged event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeHome { - logger.Warn("channel type mismatch during HomeChannelChallenged event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) - return nil - } - - if event.StateVersion < channel.StateVersion { - logger.Error("challenged state version is less than current channel state version", "channelId", chanID, "currentStateVersion", channel.StateVersion, "challengedStateVersion", event.StateVersion) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusChallenged - - expirationTime := time.Unix(int64(event.ChallengeExpiry), 0) - channel.ChallengeExpiresAt = &expirationTime - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - lastSignedState, err := tx.GetLastStateByChannelID(chanID, true) - if err != nil { - return err - } - if lastSignedState == nil { - logger.Warn("no state found for channel during HomeChannelChallenged event", "channelId", chanID) - } else if lastSignedState.Version <= event.StateVersion { - logger.Warn("last signed state version is not greater than challenged state version", "channelId", chanID, "lastSignedStateVersion", lastSignedState.Version, "challengedStateVersion", event.StateVersion) - } else { - if err := tx.ScheduleCheckpoint(lastSignedState.ID, lastSignedState.HomeLedger.BlockchainID); err != nil { - return err - } - } - - logger.Info("handled HomeChannelChallenged event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleHomeChannelClosed processes the HomeChannelClosed event emitted when a home channel is -// finalized and closed on-chain. It updates the channel status to Closed and sets the final state version. -// Once closed, no further state updates are possible for this channel. -func (s *EventHandlerService) HandleHomeChannelClosed(ctx context.Context, event *core.HomeChannelClosedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during HomeChannelClosed event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeHome { - logger.Warn("channel type mismatch during HomeChannelClosed event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusClosed - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - logger.Info("handled HomeChannelClosed event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleEscrowDepositInitiated processes the EscrowDepositInitiated event emitted when an escrow -// deposit operation begins on-chain. It updates the escrow channel status to Open, sets the state -// version, and schedules a checkpoint to finalize the deposit if a matching state exists in the database. -func (s *EventHandlerService) HandleEscrowDepositInitiated(ctx context.Context, event *core.EscrowDepositInitiatedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during EscrowDepositInitiated event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeEscrow { - logger.Warn("channel type mismatch during EscrowDepositInitiated event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusOpen - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - state, err := tx.GetStateByChannelIDAndVersion(chanID, event.StateVersion) - if err != nil { - return err - } - if state == nil { - logger.Warn("no state found for channel during EscrowDepositInitiated event", "channelId", chanID) - } else { - if err := tx.ScheduleInitiateEscrowDeposit(state.ID, state.HomeLedger.BlockchainID); err != nil { - return err - } - } - - logger.Info("handled EscrowDepositInitiated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleEscrowDepositChallenged processes the EscrowDepositChallenged event emitted when an escrow -// deposit is challenged on-chain. Similar to home channel challenges, it marks the channel as Challenged, -// sets the expiration time, and automatically schedules a checkpoint with the latest signed state -// to resolve the challenge. -func (s *EventHandlerService) HandleEscrowDepositChallenged(ctx context.Context, event *core.EscrowDepositChallengedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during EscrowDepositChallenged event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeEscrow { - logger.Warn("channel type mismatch during EscrowDepositChallenged event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) - return nil - } - - if event.StateVersion < channel.StateVersion { - logger.Error("challenged escrow deposit state version is less than current channel state version", "channelId", chanID, "currentStateVersion", channel.StateVersion, "challengedStateVersion", event.StateVersion) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusChallenged - - expirationTime := time.Unix(int64(event.ChallengeExpiry), 0) - channel.ChallengeExpiresAt = &expirationTime - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - lastSignedState, err := tx.GetLastStateByChannelID(chanID, true) - if err != nil { - return err - } - if lastSignedState == nil { - logger.Warn("no state found for channel during EscrowDepositChallenged event", "channelId", chanID) - } else if lastSignedState.Version <= event.StateVersion { - logger.Warn("last signed state version is not greater than challenged state version", "channelId", chanID, "lastSignedStateVersion", lastSignedState.Version, "challengedStateVersion", event.StateVersion) - } else { - if lastSignedState.EscrowLedger == nil { - logger.Warn("last signed state has no escrow ledger during EscrowDepositChallenged event", "channelId", chanID) - } else { - if err := tx.ScheduleFinalizeEscrowDeposit(lastSignedState.ID, lastSignedState.EscrowLedger.BlockchainID); err != nil { - return err - } - } - } - - logger.Info("handled EscrowDepositChallenged event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleEscrowDepositFinalized processes the EscrowDepositFinalized event emitted when an escrow -// deposit is successfully finalized on-chain. It updates the channel status to Closed and sets -// the final state version, completing the deposit lifecycle. -func (s *EventHandlerService) HandleEscrowDepositFinalized(ctx context.Context, event *core.EscrowDepositFinalizedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during EscrowDepositFinalized event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeEscrow { - logger.Warn("channel type mismatch during EscrowDepositFinalized event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusClosed - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - logger.Info("handled EscrowDepositFinalized event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleEscrowWithdrawalInitiated processes the EscrowWithdrawalInitiated event emitted when an escrow -// withdrawal operation begins on-chain. It updates the escrow channel status to Open and sets the state -// version to reflect the initiated withdrawal. -func (s *EventHandlerService) HandleEscrowWithdrawalInitiated(ctx context.Context, event *core.EscrowWithdrawalInitiatedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during EscrowWithdrawalInitiated event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeEscrow { - logger.Warn("channel type mismatch during EscrowWithdrawalInitiated event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusOpen - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - logger.Info("handled EscrowWithdrawalInitiated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleEscrowWithdrawalChallenged processes the EscrowWithdrawalChallenged event emitted when an escrow -// withdrawal is challenged on-chain. It marks the channel as Challenged, sets the expiration time, -// and schedules a checkpoint for escrow withdrawal with the latest signed state to resolve the challenge. -func (s *EventHandlerService) HandleEscrowWithdrawalChallenged(ctx context.Context, event *core.EscrowWithdrawalChallengedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during EscrowWithdrawalChallenged event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeEscrow { - logger.Warn("channel type mismatch during EscrowWithdrawalChallenged event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) - return nil - } - - if event.StateVersion < channel.StateVersion { - logger.Error("challenged escrow withdrawal state version is less than current channel state version", "channelId", chanID, "currentStateVersion", channel.StateVersion, "challengedStateVersion", event.StateVersion) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusChallenged - - expirationTime := time.Unix(int64(event.ChallengeExpiry), 0) - channel.ChallengeExpiresAt = &expirationTime - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - lastSignedState, err := tx.GetLastStateByChannelID(chanID, true) - if err != nil { - return err - } - if lastSignedState == nil { - logger.Warn("no state found for channel during EscrowWithdrawalChallenged event", "channelId", chanID) - } else if lastSignedState.Version <= event.StateVersion { - logger.Warn("last signed state version is not greater than challenged state version", "channelId", chanID, "lastSignedStateVersion", lastSignedState.Version, "challengedStateVersion", event.StateVersion) - } else { - if lastSignedState.EscrowLedger == nil { - logger.Warn("last signed state has no escrow ledger during EscrowWithdrawalChallenged event", "channelId", chanID) - } else { - if err := tx.ScheduleFinalizeEscrowWithdrawal(lastSignedState.ID, lastSignedState.EscrowLedger.BlockchainID); err != nil { - return err - } - } - } - - logger.Info("handled EscrowWithdrawalChallenged event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - return nil - }) -} - -// HandleEscrowWithdrawalFinalized processes the EscrowWithdrawalFinalized event emitted when an escrow -// withdrawal is successfully finalized on-chain. It updates the channel status to Closed and sets -// the final state version, completing the withdrawal lifecycle. -func (s *EventHandlerService) HandleEscrowWithdrawalFinalized(ctx context.Context, event *core.EscrowWithdrawalFinalizedEvent) error { - logger := log.FromContext(ctx) - return s.useStoreInTx(func(tx Store) error { - chanID := event.ChannelID - channel, err := tx.GetChannelByID(chanID) - if err != nil { - return err - } - if channel == nil { - logger.Warn("channel not found in DB during EscrowWithdrawalFinalized event", "channelId", chanID) - return nil - } - if channel.Type != core.ChannelTypeEscrow { - logger.Warn("channel type mismatch during EscrowWithdrawalFinalized event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) - return nil - } - - channel.StateVersion = event.StateVersion - channel.Status = core.ChannelStatusClosed - - if err := tx.UpdateChannel(*channel); err != nil { - return err - } - - logger.Info("handled EscrowWithdrawalFinalized event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) - 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 deleted file mode 100644 index e5e9afb1c..000000000 --- a/clearnode/event_handlers/service_test.go +++ /dev/null @@ -1,586 +0,0 @@ -package event_handlers - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/shopspring/decimal" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/layer-3/nitrolite/pkg/core" - "github.com/layer-3/nitrolite/pkg/log" -) - -func TestHandleHomeChannelCreated_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 - channelID := "0xHomeChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeHome, - Status: core.ChannelStatusVoid, - StateVersion: 0, - } - - event := &core.HomeChannelCreatedEvent{ - ChannelID: channelID, - StateVersion: 1, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusOpen && - ch.StateVersion == 1 - })).Return(nil) - - // Execute - err := service.HandleHomeChannelCreated(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleHomeChannelCheckpointed_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 - channelID := "0xHomeChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - expiryTime := time.Now().Add(time.Hour) - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeHome, - Status: core.ChannelStatusChallenged, - StateVersion: 3, - ChallengeExpiresAt: &expiryTime, - } - - event := &core.HomeChannelCheckpointedEvent{ - ChannelID: channelID, - StateVersion: 5, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusOpen && - ch.StateVersion == 5 - })).Return(nil) - - // Execute - err := service.HandleHomeChannelCheckpointed(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleHomeChannelChallenged_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 - channelID := "0xHomeChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeHome, - Status: core.ChannelStatusOpen, - StateVersion: 3, - } - - state := &core.State{ - ID: "state123", - Version: 6, - HomeLedger: core.Ledger{ - BlockchainID: 0, - }, - } - - event := &core.HomeChannelChallengedEvent{ - ChannelID: channelID, - StateVersion: 4, - ChallengeExpiry: challengeExpiry, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusChallenged && - ch.StateVersion == 4 && - ch.ChallengeExpiresAt != nil - })).Return(nil) - mockStore.On("GetLastStateByChannelID", channelID, true).Return(state, nil) - mockStore.On("ScheduleCheckpoint", "state123", uint64(0)).Return(nil) - - // Execute - err := service.HandleHomeChannelChallenged(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleHomeChannelClosed_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 - channelID := "0xHomeChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeHome, - Status: core.ChannelStatusOpen, - StateVersion: 5, - } - - event := &core.HomeChannelClosedEvent{ - ChannelID: channelID, - StateVersion: 10, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusClosed && - ch.StateVersion == 10 - })).Return(nil) - - // Execute - err := service.HandleHomeChannelClosed(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleEscrowDepositInitiated_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 - channelID := "0xEscrowChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeEscrow, - Status: core.ChannelStatusVoid, - StateVersion: 0, - } - - state := &core.State{ - ID: "state123", - Version: 1, - HomeLedger: core.Ledger{ - BlockchainID: 0, - }, - EscrowLedger: &core.Ledger{ - BlockchainID: 2, - }, - } - - event := &core.EscrowDepositInitiatedEvent{ - ChannelID: channelID, - StateVersion: 1, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusOpen && - ch.StateVersion == 1 - })).Return(nil) - mockStore.On("GetStateByChannelIDAndVersion", channelID, uint64(1)).Return(state, nil) - mockStore.On("ScheduleInitiateEscrowDeposit", "state123", uint64(0)).Return(nil) - - // Execute - err := service.HandleEscrowDepositInitiated(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleEscrowDepositChallenged_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 - channelID := "0xEscrowChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeEscrow, - Status: core.ChannelStatusOpen, - StateVersion: 1, - } - - state := &core.State{ - ID: "state123", - Version: 5, - EscrowLedger: &core.Ledger{ - BlockchainID: 2, - }, - } - - event := &core.EscrowDepositChallengedEvent{ - ChannelID: channelID, - StateVersion: 3, - ChallengeExpiry: challengeExpiry, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusChallenged && - ch.StateVersion == 3 && - ch.ChallengeExpiresAt != nil - })).Return(nil) - mockStore.On("GetLastStateByChannelID", channelID, true).Return(state, nil) - mockStore.On("ScheduleFinalizeEscrowDeposit", "state123", uint64(2)).Return(nil) - - // Execute - err := service.HandleEscrowDepositChallenged(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleEscrowDepositFinalized_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 - channelID := "0xEscrowChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeEscrow, - Status: core.ChannelStatusOpen, - StateVersion: 3, - } - - event := &core.EscrowDepositFinalizedEvent{ - ChannelID: channelID, - StateVersion: 5, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusClosed && - ch.StateVersion == 5 - })).Return(nil) - - // Execute - err := service.HandleEscrowDepositFinalized(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleEscrowWithdrawalInitiated_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 - channelID := "0xEscrowChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeEscrow, - Status: core.ChannelStatusVoid, - StateVersion: 0, - } - - event := &core.EscrowWithdrawalInitiatedEvent{ - ChannelID: channelID, - StateVersion: 1, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusOpen && - ch.StateVersion == 1 - })).Return(nil) - - // Execute - err := service.HandleEscrowWithdrawalInitiated(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleEscrowWithdrawalChallenged_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 - channelID := "0xEscrowChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeEscrow, - Status: core.ChannelStatusOpen, - StateVersion: 1, - } - - state := &core.State{ - ID: "state123", - Version: 5, - EscrowLedger: &core.Ledger{ - BlockchainID: 2, - }, - } - - event := &core.EscrowWithdrawalChallengedEvent{ - ChannelID: channelID, - StateVersion: 3, - ChallengeExpiry: challengeExpiry, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusChallenged && - ch.StateVersion == 3 && - ch.ChallengeExpiresAt != nil - })).Return(nil) - mockStore.On("GetLastStateByChannelID", channelID, true).Return(state, nil) - mockStore.On("ScheduleFinalizeEscrowWithdrawal", "state123", uint64(2)).Return(nil) - - // Execute - err := service.HandleEscrowWithdrawalChallenged(ctx, event) - - // Assert - require.NoError(t, err) - mockStore.AssertExpectations(t) -} - -func TestHandleEscrowWithdrawalFinalized_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 - channelID := "0xEscrowChannel123" - userWallet := "0x1234567890123456789012345678901234567890" - - channel := &core.Channel{ - ChannelID: channelID, - UserWallet: userWallet, - Asset: "usdc", - Type: core.ChannelTypeEscrow, - Status: core.ChannelStatusOpen, - StateVersion: 3, - } - - event := &core.EscrowWithdrawalFinalizedEvent{ - ChannelID: channelID, - StateVersion: 5, - } - - // Mock expectations - mockStore.On("GetChannelByID", channelID).Return(channel, nil) - mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { - return ch.ChannelID == channelID && - ch.Status == core.ChannelStatusClosed && - ch.StateVersion == 5 - })).Return(nil) - - // Execute - err := service.HandleEscrowWithdrawalFinalized(ctx, event) - - // Assert - 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 deleted file mode 100644 index aab414c32..000000000 --- a/clearnode/event_handlers/testing.go +++ /dev/null @@ -1,76 +0,0 @@ -package event_handlers - -import ( - "github.com/shopspring/decimal" - "github.com/stretchr/testify/mock" - - "github.com/layer-3/nitrolite/pkg/core" -) - -// MockStore is a mock implementation of the Store interface for testing -type MockStore struct { - mock.Mock -} - -// GetLastStateByChannelID mocks retrieving the last state for a channel -func (m *MockStore) GetLastStateByChannelID(channelID string, signed bool) (*core.State, error) { - args := m.Called(channelID, signed) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*core.State), args.Error(1) -} - -// GetStateByChannelIDAndVersion mocks retrieving a specific state version for a channel -func (m *MockStore) GetStateByChannelIDAndVersion(channelID string, version uint64) (*core.State, error) { - args := m.Called(channelID, version) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*core.State), args.Error(1) -} - -// UpdateChannel mocks updating a channel in the database -func (m *MockStore) UpdateChannel(channel core.Channel) error { - args := m.Called(channel) - return args.Error(0) -} - -// GetChannelByID mocks retrieving a channel by its ID -func (m *MockStore) GetChannelByID(channelID string) (*core.Channel, error) { - args := m.Called(channelID) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*core.Channel), args.Error(1) -} - -// ScheduleCheckpoint mocks scheduling a checkpoint operation -func (m *MockStore) ScheduleCheckpoint(stateID string, chainID uint64) error { - args := m.Called(stateID, chainID) - return args.Error(0) -} - -// ScheduleInitiateEscrowDeposit mocks scheduling an escrow deposit checkpoint -func (m *MockStore) ScheduleInitiateEscrowDeposit(stateID string, chainID uint64) error { - args := m.Called(stateID, chainID) - return args.Error(0) -} - -// ScheduleFinalizeEscrowDeposit mocks scheduling an escrow deposit checkpoint -func (m *MockStore) ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error { - args := m.Called(stateID, chainID) - return args.Error(0) -} - -// ScheduleFinalizeEscrowWithdrawal mocks scheduling an escrow withdrawal checkpoint -func (m *MockStore) ScheduleFinalizeEscrowWithdrawal(stateID string, chainID uint64) error { - 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/metrics/exporter.go b/clearnode/metrics/exporter.go deleted file mode 100644 index 3148ae142..000000000 --- a/clearnode/metrics/exporter.go +++ /dev/null @@ -1,365 +0,0 @@ -package metrics - -import ( - "fmt" - "strconv" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/shopspring/decimal" - - "github.com/layer-3/nitrolite/pkg/app" - "github.com/layer-3/nitrolite/pkg/core" - "github.com/layer-3/nitrolite/pkg/rpc" -) - -const ( - // MetricNamespace is the common namespace for all ClearNode metrics. - MetricNamespace = "clearnode" -) - -var ( - _ RuntimeMetricExporter = (*runtimeMetricExporter)(nil) - _ StoreMetricExporter = (*storeMetricExporter)(nil) -) - -type storeMetricExporter struct { - 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_total", - Help: "Current total number of app sessions", - }, []string{"application", "status"}), - channelsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricNamespace, - 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") - } - - return m, nil -} - -func (m *storeMetricExporter) SetAppSessions(applicationID string, status app.AppSessionStatus, count uint64) { - m.appSessionsTotal.WithLabelValues(applicationID, status.String()).Set(float64(count)) -} - -func (m *storeMetricExporter) SetChannels(asset string, status core.ChannelStatus, count uint64) { - 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) - userStatesTotal *prometheus.CounterVec - transactionsTotal *prometheus.CounterVec - transactionsAmountTotal *prometheus.CounterVec - channelStateSigValidationsTotal *prometheus.CounterVec - - // api/rpc_router.go - rpcMessagesTotal *prometheus.CounterVec - rpcRequestsTotal *prometheus.CounterVec - rpcRequestDurationSeconds *prometheus.HistogramVec - rpcConnectionsTotal *prometheus.GaugeVec - - // api/app_session_v1 - appStateUpdates *prometheus.CounterVec - appSessionUpdateSigValidationsTotal *prometheus.CounterVec - - // Blockchain Worker - blockchainActionsTotal *prometheus.CounterVec - - // Event Listener - blockchainEventsTotal *prometheus.CounterVec - - // Metric Worker - channelSessionKeysTotal prometheus.Counter - appSessionKeysTotal prometheus.Counter -} - -// RuntimeMetricExporter exposes metrics related to runtime operations, such as API requests, channel state validations, and blockchain interactions. -func NewRuntimeMetricExporter(reg prometheus.Registerer) (RuntimeMetricExporter, error) { - m := &runtimeMetricExporter{ - // Shared - userStatesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "user_states_total", - Help: "Total number of user states", - }, []string{"asset", "home_blockchain_id", "transition"}), - transactionsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "transactions_total", - Help: "Total number of transactions", - }, []string{"asset", "tx_type"}), - transactionsAmountTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "transactions_amount_total", - Help: "Total amount of transactions processed", - }, []string{"asset", "tx_type"}), - channelSessionKeysTotal: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "channel_session_keys_total", - Help: "Total number of channel session keys issued", - }), - appSessionKeysTotal: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "app_session_keys_total", - Help: "Total number of app session keys issued", - }), - channelStateSigValidationsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "channel_state_sig_validations_total", - Help: "Total number of channel state signature validations", - }, []string{"sig_type", "result"}), - - // api/rpc_router - rpcMessagesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "rpc_messages_total", - Help: "Total number of RPC messages", - }, []string{"msg_type", "method"}), - rpcRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "rpc_requests_total", - Help: "Total number of RPC requests", - }, []string{"method", "path", "status"}), - rpcRequestDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: MetricNamespace, - Name: "rpc_request_duration_seconds", - Help: "Duration of RPC requests in seconds", - Buckets: []float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, - }, []string{"method", "path", "status"}), - rpcConnectionsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: MetricNamespace, - Name: "rpc_connections_active", - Help: "Current number of active RPC connections", - }, []string{"region", "origin"}), - - // api/app_session_v1 - appStateUpdates: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "app_state_updates_total", - Help: "Total number of app state updates", - }, []string{"application"}), - appSessionUpdateSigValidationsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "app_session_update_sig_validations_total", - Help: "Total number of app session update signature validations", - }, []string{"application", "sig_type", "result"}), - - // Blockchain Worker - blockchainActionsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "blockchain_actions_total", - Help: "Total number of blockchain actions", - }, []string{"asset", "blockchain_id", "action_type", "result"}), - - // Event Listener - blockchainEventsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricNamespace, - Name: "blockchain_events_total", - Help: "Total number of blockchain events processed", - }, []string{"blockchain_id", "process_result"}), - } - - if reg != nil { - reg.MustRegister( - m.userStatesTotal, - m.transactionsTotal, - m.transactionsAmountTotal, - m.channelStateSigValidationsTotal, - m.rpcMessagesTotal, - m.rpcRequestsTotal, - m.rpcRequestDurationSeconds, - m.rpcConnectionsTotal, - m.appStateUpdates, - m.appSessionUpdateSigValidationsTotal, - m.blockchainActionsTotal, - m.blockchainEventsTotal, - m.channelSessionKeysTotal, - m.appSessionKeysTotal, - ) - } else { - return nil, fmt.Errorf("prometheus registerer not provided") - } - - return m, nil -} - -// Shared -func (m *runtimeMetricExporter) IncUserState(asset string, homeBlockchainID uint64, transition core.TransitionType) { - homeBlockchainIDStr := strconv.FormatUint(homeBlockchainID, 10) - m.userStatesTotal.WithLabelValues(asset, homeBlockchainIDStr, transition.String()).Inc() -} - -func (m *runtimeMetricExporter) RecordTransaction(asset string, txType core.TransactionType, amount decimal.Decimal) { - m.transactionsTotal.WithLabelValues(asset, txType.String()).Inc() - m.transactionsAmountTotal.WithLabelValues(asset, txType.String()).Add(amount.InexactFloat64()) -} - -// api/rpc_router -func (m *runtimeMetricExporter) IncRPCMessage(msgType rpc.MsgType, method string) { - m.rpcMessagesTotal.WithLabelValues(msgType.String(), method).Inc() -} - -func (m *runtimeMetricExporter) IncRPCRequest(method, path string, success bool) { - result := ActionResultFailed - if success { - result = ActionResultSuccess - } - m.rpcRequestsTotal.WithLabelValues(method, path, result.String()).Inc() -} - -func (m *runtimeMetricExporter) ObserveRPCDuration(method, path string, success bool, duration time.Duration) { - result := ActionResultFailed - if success { - result = ActionResultSuccess - } - m.rpcRequestDurationSeconds.WithLabelValues(method, path, result.String()).Observe(duration.Seconds()) -} - -func (m *runtimeMetricExporter) SetRPCConnections(region, origin string, count uint32) { - m.rpcConnectionsTotal.WithLabelValues(region, origin).Set(float64(count)) -} - -// api/app_session_v1 -func (m *runtimeMetricExporter) IncAppStateUpdate(applicationID string) { - m.appStateUpdates.WithLabelValues(applicationID).Inc() -} - -func (m *runtimeMetricExporter) IncAppSessionUpdateSigValidation(applicationID string, signerType app.AppSessionSignerTypeV1, result bool) { - res := ActionResultFailed - if result { - res = ActionResultSuccess - } - m.appSessionUpdateSigValidationsTotal.WithLabelValues(applicationID, signerType.String(), res.String()).Inc() -} - -func (m *runtimeMetricExporter) IncChannelStateSigValidation(sigType core.ChannelSignerType, result bool) { - res := ActionResultFailed - if result { - res = ActionResultSuccess - } - m.channelStateSigValidationsTotal.WithLabelValues(sigType.String(), res.String()).Inc() -} - -// Blockchain Worker -func (m *runtimeMetricExporter) IncBlockchainAction(asset string, blockchainID uint64, actionType string, result bool) { - stringBlockchainID := strconv.FormatUint(blockchainID, 10) - res := ActionResultFailed - if result { - res = ActionResultSuccess - } - m.blockchainActionsTotal.WithLabelValues(asset, stringBlockchainID, actionType, res.String()).Inc() -} - -// Event Listener -func (m *runtimeMetricExporter) IncBlockchainEvent(blockchainID uint64, result bool) { - stringBlockchainID := strconv.FormatUint(blockchainID, 10) - res := ActionResultFailed - if result { - res = ActionResultSuccess - } - m.blockchainEventsTotal.WithLabelValues(stringBlockchainID, res.String()).Inc() -} - -// Metric Worker -func (m *runtimeMetricExporter) IncChannelSessionKeys() { - m.channelSessionKeysTotal.Inc() -} - -func (m *runtimeMetricExporter) IncAppSessionKeys() { - m.appSessionKeysTotal.Inc() -} - -type ActionResult string - -const ( - ActionResultSuccess ActionResult = "success" - ActionResultFailed ActionResult = "failed" -) - -func (res ActionResult) String() string { - return string(res) -} - -// api/channel_v1 -// -* `user_states_total{asset, home_blockchain_id, transition}` -// -* `transactions_total{asset, tx_type}` -// -* `transactions_amount_total{asset, tx_type}` -// - `channel_state_validations_total{sig_type, result}` - -// api/rpc_router.go -// - `rpc_messages_total{msg_type, method}` -// - `rpc_requests_total{method, status}` -// - `rpc_request_duration_seconds{method, path, status}` -// - `rpc_connections_total{region}` - -// api/app_session_v1 -// -* `app_state_updates{application}` -// - `app_session_update_sig_validations_total{application, sig_type, result}` -// -* `user_states_total{asset, home_blockchain_id, transition}` -// -* `transactions_total{asset, tx_type}` -// -* `transactions_amount_total{asset, tx_type}` -// - `channel_state_sig_validations_total{sig_type, result}` - -// Blockchain Worker -// -* `blockchain_actions_total{asset, blockchain_id, action_type, result}` - -// Event Listener -// -* `blockchain_events_total{blockchain_id, process_result}` - -// By the end of this story a separate metric worker should expose the following metrics: - -// metric worker -// - `channel_session_keys_total` -// - `app_session_keys_total` -// - `app_sessions_total{application,status}` -// - `channels_total{asset,status}` diff --git a/clearnode/metrics/interface.go b/clearnode/metrics/interface.go deleted file mode 100644 index db0df3f06..000000000 --- a/clearnode/metrics/interface.go +++ /dev/null @@ -1,66 +0,0 @@ -package metrics - -import ( - "time" - - "github.com/shopspring/decimal" - - "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. -type RuntimeMetricExporter interface { - // Shared - IncUserState(asset string, homeBlockchainID uint64, transition core.TransitionType) // + - RecordTransaction(asset string, txType core.TransactionType, amount decimal.Decimal) // + - IncChannelStateSigValidation(sigType core.ChannelSignerType, success bool) // + - IncChannelSessionKeys() // + - IncAppSessionKeys() // + - - // api/rpc_router - IncRPCMessage(msgType rpc.MsgType, method string) // + - IncRPCRequest(method, path string, success bool) // + - ObserveRPCDuration(method, path string, success bool, durationSecs time.Duration) // + - SetRPCConnections(region, origin string, count uint32) // + - - // api/app_session_v1 - IncAppStateUpdate(applicationID string) // + - IncAppSessionUpdateSigValidation(applicationID string, sigType app.AppSessionSignerTypeV1, success bool) // + - - // Blockchain Worker - IncBlockchainAction(asset string, blockchainID uint64, actionType string, success bool) // + - - // Event Listener - IncBlockchainEvent(blockchainID uint64, handledSuccessfully bool) // + -} - -// noopRuntimeMetricExporter is a no-op implementation for use in tests. -type noopRuntimeMetricExporter struct{} - -func NewNoopRuntimeMetricExporter() RuntimeMetricExporter { return noopRuntimeMetricExporter{} } -func (noopRuntimeMetricExporter) IncUserState(string, uint64, core.TransitionType) {} -func (noopRuntimeMetricExporter) RecordTransaction(string, core.TransactionType, decimal.Decimal) {} -func (noopRuntimeMetricExporter) IncChannelStateSigValidation(core.ChannelSignerType, bool) {} -func (noopRuntimeMetricExporter) IncChannelSessionKeys() {} -func (noopRuntimeMetricExporter) IncAppSessionKeys() {} -func (noopRuntimeMetricExporter) IncRPCMessage(rpc.MsgType, string) {} -func (noopRuntimeMetricExporter) IncRPCRequest(string, string, bool) {} -func (noopRuntimeMetricExporter) ObserveRPCDuration(string, string, bool, time.Duration) {} -func (noopRuntimeMetricExporter) SetRPCConnections(string, string, uint32) {} -func (noopRuntimeMetricExporter) IncAppStateUpdate(string) {} -func (noopRuntimeMetricExporter) IncAppSessionUpdateSigValidation(string, app.AppSessionSignerTypeV1, bool) { -} -func (noopRuntimeMetricExporter) IncBlockchainAction(string, uint64, string, bool) { -} -func (noopRuntimeMetricExporter) IncBlockchainEvent(uint64, bool) {} - -// StoreMetricExporter defines the interface for setting metrics that are stored and updated by a separate metric worker. -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 deleted file mode 100644 index c65733b59..000000000 --- a/clearnode/runtime.go +++ /dev/null @@ -1,324 +0,0 @@ -package main - -import ( - "context" - "embed" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ilyakaznacheev/cleanenv" - "github.com/joho/godotenv" - "github.com/prometheus/client_golang/prometheus" - - "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 -var embedMigrations embed.FS - -var Version = "v1.0.0" // set at build time with -ldflags "-X main.Version=x.y.z" - -type Backbone struct { - NodeVersion string - 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 - Logger log.Logger - RuntimeMetrics metrics.RuntimeMetricExporter - StoreMetrics metrics.StoreMetricExporter - closers []func() error -} - -// Close releases resources held by the backbone (e.g., KMS client connections). -func (b *Backbone) Close() error { - var firstErr error - for _, fn := range b.closers { - if err := fn(); err != nil && firstErr == nil { - firstErr = err - } - } - return firstErr -} - -type Config struct { - Database database.DatabaseConfig - ChannelMinChallengeDuration uint32 `yaml:"channel_min_challenge_duration" env:"CLEARNODE_CHANNEL_MIN_CHALLENGE_DURATION" env-default:"86400"` // 24 hours - SignerType string `yaml:"signer_type" env:"CLEARNODE_SIGNER_TYPE" env-default:"key"` // "key" or "gcp-kms" - 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"` -} - -// InitBackbone initializes the backbone components of the application. -func InitBackbone() *Backbone { - // ------------------------------------------------ - // Logger - // ------------------------------------------------ - - var loggerConf log.Config - if err := cleanenv.ReadEnv(&loggerConf); err != nil { - panic("failed to read logger config from env: " + err.Error()) - } - logger := log.NewZapLogger(loggerConf) - logger = logger.WithName("main") - - // ------------------------------------------------ - // (Preparation) - // ------------------------------------------------ - - configDirPath := os.Getenv("CLEARNODE_CONFIG_DIR_PATH") - if configDirPath == "" { - configDirPath = "." - } - - configDotEnvPath := filepath.Join(configDirPath, ".env") - logger.Info("loading .env file", "path", configDotEnvPath) - if err := godotenv.Load(configDotEnvPath); err != nil { - logger.Warn(".env file not found") - } - - var conf Config - if err := cleanenv.ReadEnv(&conf); err != nil { - logger.Fatal("failed to read env", "err", err) - } - - logger.Info("config loaded", "version", Version) - - // ------------------------------------------------ - // Database Store - // ------------------------------------------------ - - db, err := database.ConnectToDB(conf.Database, embedMigrations) - if err != nil { - logger.Fatal("failed to load database store", "error", err) - } - dbStore := database.NewDBStore(db) - - // ------------------------------------------------ - // Memory Store - // ------------------------------------------------ - - memoryStore, err := memory.NewMemoryStoreV1FromConfig(configDirPath) - if err != nil { - 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 - // ------------------------------------------------ - - var ( - stateSigner, txSigner sign.Signer - signerErr error - closers []func() error - ) - - switch conf.SignerType { - case "key": - if conf.SignerKey == "" { - logger.Fatal("CLEARNODE_SIGNER_KEY is required when CLEARNODE_SIGNER_TYPE=key") - } - txSigner, signerErr = sign.NewEthereumRawSigner(conf.SignerKey) - if signerErr != nil { - logger.Fatal("failed to initialise tx signer", "error", signerErr) - } - case "gcp-kms": - if conf.GCPKMSKeyName == "" { - logger.Fatal("CLEARNODE_GCP_KMS_KEY_NAME is required when CLEARNODE_SIGNER_TYPE=gcp-kms") - } - kmsCtx, kmsCancel := context.WithTimeout(context.Background(), 10*time.Second) - defer kmsCancel() - kmsSigner, kmsErr := gcp.NewSigner(kmsCtx, conf.GCPKMSKeyName) - if kmsErr != nil { - logger.Fatal("failed to initialise GCP KMS signer", "error", kmsErr) - } - closers = append(closers, kmsSigner.Close) - txSigner = kmsSigner - default: - logger.Fatal("unsupported CLEARNODE_SIGNER_TYPE", "type", conf.SignerType) - } - stateSigner, signerErr = sign.NewEthereumMsgSignerFromRaw(txSigner) - if signerErr != nil { - logger.Fatal("failed to wrap KMS signer as state signer", "error", signerErr) - } - - logger.Info("signer initialized", "type", conf.SignerType, "address", stateSigner.PublicKey().Address()) - - // ------------------------------------------------ - // Metrics - // ------------------------------------------------ - - runtimeMetrics, err := metrics.NewRuntimeMetricExporter(prometheus.DefaultRegisterer) - if err != nil { - logger.Fatal("failed to initialize runtime metric exporter", "error", err) - } - storeMetrics, err := metrics.NewStoreMetricExporter(prometheus.DefaultRegisterer) - if err != nil { - logger.Fatal("failed to initialize store metric exporter", "error", err) - } - - // ------------------------------------------------ - // RPC Node - // ------------------------------------------------ - - rpcNode, err := rpc.NewWebsocketNode(rpc.WebsocketNodeConfig{ - Logger: logger, - ObserveConnections: runtimeMetrics.SetRPCConnections, - WsConnProcessBufferSize: conf.WsProcessBufferSize, - WsConnWriteBufferSize: conf.WsWriteBufferSize, - }) - if err != nil { - logger.Fatal("failed to initialize RPC node", "error", err) - } - - // ------------------------------------------------ - // Blockchain RPCs - // ------------------------------------------------ - - blockchains, err := memoryStore.GetBlockchains() - if err != nil { - logger.Fatal("failed to get blockchains", "error", err) - } - - blockchainRPCs := make(map[uint64]string) - for _, bc := range blockchains { - envVarName := "CLEARNODE_BLOCKCHAIN_RPC_" + strings.ToUpper(bc.Name) - rpcURL := os.Getenv(envVarName) - if rpcURL == "" { - logger.Fatal("blockchain RPC URL not set in env", "blockchainID", bc.ID, "env_var", envVarName) - } - - // Test connection - if err := checkChainId(rpcURL, bc.ID); err != nil { - logger.Fatal("failed to verify blockchain RPC", "blockchainID", bc.ID, "error", err) - } - - // Verify ChannelHub version - channelHubAddress := common.HexToAddress(bc.ChannelHubAddress) - if err := checkChannelHubVersion(rpcURL, channelHubAddress, core.ChannelHubVersion); err != nil { - logger.Fatal("failed to verify ChannelHub version", "blockchainID", bc.ID, "address", bc.ChannelHubAddress, "error", err) - } - - blockchainRPCs[bc.ID] = rpcURL - } - - return &Backbone{ - NodeVersion: Version, - 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, - Logger: logger, - RuntimeMetrics: runtimeMetrics, - StoreMetrics: storeMetrics, - closers: closers, - } -} - -// checkChainId connects to an RPC endpoint and verifies it returns the expected chain ID. -// This ensures the RPC URL points to the correct blockchain network. -// The function uses a 5-second timeout for the connection and chain ID query. -func checkChainId(blockchainRPC string, expectedChainID uint64) error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - client, err := ethclient.DialContext(ctx, blockchainRPC) - if err != nil { - return fmt.Errorf("failed to connect to blockchain RPC: %w", err) - } - defer client.Close() - - chainID, err := client.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to get chain ID from blockchain RPC: %w", err) - } - - if chainID.Uint64() != expectedChainID { - return fmt.Errorf("unexpected chain ID from blockchain RPC: got %d, want %d", chainID.Uint64(), expectedChainID) - } - - return nil -} - -// checkChannelHubVersion verifies that the ChannelHub contract at the given address -// has the expected VERSION constant value. -// The function uses a 5-second timeout for the connection and contract calls. -func checkChannelHubVersion(blockchainRPC string, channelHubAddress common.Address, expectedVersion uint8) error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - client, err := ethclient.DialContext(ctx, blockchainRPC) - if err != nil { - return fmt.Errorf("failed to connect to blockchain RPC: %w", err) - } - defer client.Close() - - channelHub, err := evm.NewChannelHubCaller(channelHubAddress, client) - if err != nil { - return fmt.Errorf("failed to create ChannelHub caller: %w", err) - } - - fetchedVersion, err := channelHub.VERSION(nil) - if err != nil { - return fmt.Errorf("failed to get ChannelHub version: %w", err) - } - - if fetchedVersion != expectedVersion { - return fmt.Errorf("configured and fetched ChannelHub version mismatch: got %d, want %d", fetchedVersion, expectedVersion) - } - - return nil -} diff --git a/clearnode/scripts/auto_tag.sh b/clearnode/scripts/auto_tag.sh deleted file mode 100755 index d946ded73..000000000 --- a/clearnode/scripts/auto_tag.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# Get the first arg to the script as version prefix -version_prefix=$1 -if [ -z "$version_prefix" ]; then - echo "Usage: $0 " - exit 1 -fi - -last_rc_tag=$(git ls-remote -t | grep -Eo "${version_prefix}-rc\.[0-9]+$" | sort -V -r | head -n 1) - -new_rc_tag="" -if [ -z "$last_rc_tag" ]; then - # No RC tags exist - new_rc_tag="${version_prefix}-rc.0" -else - # Last RC tag is the latest stable tag - new_rc_tag="${version_prefix}-rc.$((${last_rc_tag##*.}+1))" -fi - -git tag "$new_rc_tag" -git push origin "$new_rc_tag" diff --git a/clearnode/scripts/db-init.sh b/clearnode/scripts/db-init.sh deleted file mode 100755 index c9519f5be..000000000 --- a/clearnode/scripts/db-init.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo 'Waiting for database to be ready...' - -until pg_isready -h database -p 5432 -U ${POSTGRES_USER}; do - echo 'Waiting for database connection...' - sleep 2 -done - -psql -h database -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c " - CREATE TABLE IF NOT EXISTS goose_db_version ( - id serial PRIMARY KEY, - version_id int8 NOT NULL, - is_applied boolean NOT NULL DEFAULT true, - tstamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP - ); -" - -echo 'Checking for migration files...' - -echo 'Running pending migrations...' -for migration in /migrations/*.sql; do - filename=$(basename $migration) && - version=$(echo $filename | grep -o '^[0-9]\+') && - - if ! psql -h database -U ${POSTGRES_USER} -d ${POSTGRES_DB} -tAc "SELECT 1 FROM goose_db_version WHERE version_id = $version" | grep -q 1; then - echo "Applying migration: $filename" - sed -n '/^-- +goose Up/,/^-- +goose Down/p' $migration | - grep -v '^-- +goose' | - psql -h database -U ${POSTGRES_USER} -d ${POSTGRES_DB} && - psql -h database -U ${POSTGRES_USER} -d ${POSTGRES_DB} -c "INSERT INTO goose_db_version (version_id) VALUES ($version);" && - echo "Successfully applied: $filename" - fi -done - -echo '--- Starting Token Seeding Process ---' -export PSQL="psql -h database -U ${POSTGRES_USER} -d ${POSTGRES_DB}" -chmod +x /scripts/seed-tokens.sh -/scripts/seed-tokens.sh || echo "Token seeding failed, but continuing..." -echo '--- Token Seeding Process Complete ---' diff --git a/clearnode/scripts/seed-tokens.sh b/clearnode/scripts/seed-tokens.sh deleted file mode 100755 index 4b1a5f14b..000000000 --- a/clearnode/scripts/seed-tokens.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -TABLE="${TABLE:-assets}" -PSQL="${PSQL:-psql}" -REQUIRED_PROPS=("TOKEN" "CHAIN_ID" "SYMBOL" "DECIMALS") -UPSERT="${UPSERT:-1}" -sqlq() { - local s="${1//\'/\'\'}" - printf "'%s'" "$s" -} -is_uint() { [[ "${1:-}" =~ ^[0-9]+$ ]]; } - -declare -A V -declare -A IDX -while IFS= read -r name; do - if [[ "$name" =~ ^TOKEN_([A-Za-z0-9]+)_(TOKEN|ADDRESS|CHAIN_ID|SYMBOL|DECIMALS)$ ]]; then - idx="${BASH_REMATCH[1]}" - prop="${BASH_REMATCH[2]}" - [[ "$prop" == "ADDRESS" ]] && prop="TOKEN" - val="${!name-}" - V["$idx|$prop"]="$val" - IDX["$idx"]=1 - fi -done < <(compgen -e) - -if [ "${#IDX[@]}" -eq 0 ]; then - echo "No TOKEN__{TOKEN|ADDRESS|CHAIN_ID|SYMBOL|DECIMALS} variables found." >&2 - exit 1 -fi - -sql="BEGIN;\n" -for idx in "${!IDX[@]}"; do - missing=() - for p in "${REQUIRED_PROPS[@]}"; do - [[ -n "${V["$idx|$p"]+x}" ]] || missing+=("$p") - done - if ((${#missing[@]})); then - echo "Skip index '$idx': missing ${missing[*]}" >&2 - continue - fi - - token="${V["$idx|TOKEN"]}" - chain_id="${V["$idx|CHAIN_ID"]}" - symbol="${V["$idx|SYMBOL"]}" - decimals="${V["$idx|DECIMALS"]}" - - is_uint "$chain_id" || { echo "Skip $idx: CHAIN_ID not uint ($chain_id)"; continue; } - is_uint "$decimals" || { echo "Skip $idx: DECIMALS not uint ($decimals)"; continue; } - - if [ "$UPSERT" = "1" ]; then - stmt=$( - cat <&2 - exit 0 -fi - -printf "%b" "$sql" | $PSQL -X -v ON_ERROR_STOP=1 diff --git a/clearnode/store/database/channel_session_key_state.go b/clearnode/store/database/channel_session_key_state.go deleted file mode 100644 index aa41159a7..000000000 --- a/clearnode/store/database/channel_session_key_state.go +++ /dev/null @@ -1,190 +0,0 @@ -package database - -import ( - "fmt" - "strings" - "time" - - "github.com/layer-3/nitrolite/pkg/core" - "gorm.io/gorm" -) - -// ChannelSessionKeyStateV1 represents a channel session key state in the database. -type ChannelSessionKeyStateV1 struct { - ID string `gorm:"column:id;primaryKey"` - UserAddress string `gorm:"column:user_address;not null;uniqueIndex:idx_channel_session_key_states_v1_user_key_ver,priority:1"` - SessionKey string `gorm:"column:session_key;not null;uniqueIndex:idx_channel_session_key_states_v1_user_key_ver,priority:2"` - Version uint64 `gorm:"column:version;not null;uniqueIndex:idx_channel_session_key_states_v1_user_key_ver,priority:3"` - Assets []ChannelSessionKeyAssetV1 `gorm:"foreignKey:SessionKeyStateID;references:ID"` - MetadataHash string `gorm:"column:metadata_hash;type:char(66);not null"` - ExpiresAt time.Time `gorm:"column:expires_at;not null"` - UserSig string `gorm:"column:user_sig;not null"` - CreatedAt time.Time -} - -func (ChannelSessionKeyStateV1) TableName() string { - return "channel_session_key_states_v1" -} - -// ChannelSessionKeyAssetV1 links a channel session key state to an asset. -type ChannelSessionKeyAssetV1 struct { - SessionKeyStateID string `gorm:"column:session_key_state_id;not null;primaryKey;priority:1"` - Asset string `gorm:"column:asset;not null;primaryKey;priority:2;index"` -} - -func (ChannelSessionKeyAssetV1) TableName() string { - return "channel_session_key_assets_v1" -} - -// StoreChannelSessionKeyState stores a new channel session key state version. -func (s *DBStore) StoreChannelSessionKeyState(state core.ChannelSessionKeyStateV1) error { - userAddress := strings.ToLower(state.UserAddress) - sessionKey := strings.ToLower(state.SessionKey) - - id, err := core.GenerateSessionKeyStateIDV1(userAddress, sessionKey, state.Version) - if err != nil { - return fmt.Errorf("failed to generate session key state ID: %w", err) - } - - metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(state.Version, state.Assets, state.ExpiresAt.Unix()) - if err != nil { - return fmt.Errorf("failed to compute metadata hash: %w", err) - } - - dbState := ChannelSessionKeyStateV1{ - ID: id, - UserAddress: userAddress, - SessionKey: sessionKey, - Version: state.Version, - MetadataHash: strings.ToLower(metadataHash.Hex()), - ExpiresAt: state.ExpiresAt.UTC(), - UserSig: state.UserSig, - } - - if err := s.db.Create(&dbState).Error; err != nil { - return fmt.Errorf("failed to store channel session key state: %w", err) - } - - if len(state.Assets) > 0 { - assets := make([]ChannelSessionKeyAssetV1, len(state.Assets)) - for i, asset := range state.Assets { - assets[i] = ChannelSessionKeyAssetV1{ - SessionKeyStateID: id, - Asset: strings.ToLower(asset), - } - } - if err := s.db.Create(&assets).Error; err != nil { - return fmt.Errorf("failed to store channel session key assets: %w", err) - } - } - - return nil -} - -// GetLastChannelSessionKeyStates retrieves the latest channel session key states for a user with optional filtering. -// Returns only the highest-version row per session key that has not expired. -func (s *DBStore) GetLastChannelSessionKeyStates(wallet string, sessionKey *string) ([]core.ChannelSessionKeyStateV1, error) { - wallet = strings.ToLower(wallet) - - subQuery := s.db.Model(&ChannelSessionKeyStateV1{}). - Select("user_address, session_key, MAX(version) as max_version"). - Where("user_address = ?", wallet). - Group("user_address, session_key") - - if sessionKey != nil && *sessionKey != "" { - subQuery = subQuery.Where("session_key = ?", strings.ToLower(*sessionKey)) - } - - query := s.db. - Joins("JOIN (?) AS latest ON channel_session_key_states_v1.user_address = latest.user_address AND channel_session_key_states_v1.session_key = latest.session_key AND channel_session_key_states_v1.version = latest.max_version", subQuery). - Preload("Assets"). - Order("channel_session_key_states_v1.created_at DESC") - - var dbStates []ChannelSessionKeyStateV1 - if err := query.Find(&dbStates).Error; err != nil { - if err == gorm.ErrRecordNotFound { - return []core.ChannelSessionKeyStateV1{}, nil - } - return nil, fmt.Errorf("failed to get channel session key states: %w", err) - } - - states := make([]core.ChannelSessionKeyStateV1, len(dbStates)) - for i, dbState := range dbStates { - states[i] = dbChannelSessionKeyStateToCore(&dbState) - } - - return states, nil -} - -// GetLastChannelSessionKeyVersion returns the latest version of a channel session key state. -// Returns 0 if no state exists. -func (s *DBStore) GetLastChannelSessionKeyVersion(wallet, sessionKey string) (uint64, error) { - wallet = strings.ToLower(wallet) - sessionKey = strings.ToLower(sessionKey) - - var result struct { - Version uint64 - } - err := s.db.Model(&ChannelSessionKeyStateV1{}). - Select("version"). - Where("user_address = ? AND session_key = ?", wallet, sessionKey). - Order("version DESC"). - Take(&result).Error - - if err != nil { - if err == gorm.ErrRecordNotFound { - return 0, nil - } - return 0, fmt.Errorf("failed to check channel session key state: %w", err) - } - - return result.Version, nil -} - -// ValidateChannelSessionKeyForAsset checks in a single query that: -// - a session key state exists for the (wallet, sessionKey) pair, -// - it is the latest version, -// - it is not expired, -// - the asset is in the allowed list, -// - the metadata hash matches. -func (s *DBStore) ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) { - wallet = strings.ToLower(wallet) - sessionKey = strings.ToLower(sessionKey) - asset = strings.ToLower(asset) - metadataHash = strings.ToLower(metadataHash) - - now := time.Now().UTC() - - maxVersionSubQ := s.db.Model(&ChannelSessionKeyStateV1{}). - Select("MAX(version)"). - Where("user_address = ? AND session_key = ?", wallet, sessionKey) - - var count int64 - err := s.db.Model(&ChannelSessionKeyStateV1{}). - Where("user_address = ? AND session_key = ? AND expires_at > ? AND metadata_hash = ? AND version = (?)", - wallet, sessionKey, now, metadataHash, maxVersionSubQ). - Joins("JOIN channel_session_key_assets_v1 ON channel_session_key_assets_v1.session_key_state_id = channel_session_key_states_v1.id AND channel_session_key_assets_v1.asset = ?", asset). - Count(&count).Error - - if err != nil { - return false, fmt.Errorf("failed to validate session key for asset: %w", err) - } - - return count > 0, nil -} - -func dbChannelSessionKeyStateToCore(dbState *ChannelSessionKeyStateV1) core.ChannelSessionKeyStateV1 { - assets := make([]string, len(dbState.Assets)) - for i, a := range dbState.Assets { - assets[i] = a.Asset - } - - return core.ChannelSessionKeyStateV1{ - UserAddress: dbState.UserAddress, - SessionKey: dbState.SessionKey, - Version: dbState.Version, - Assets: assets, - ExpiresAt: dbState.ExpiresAt, - UserSig: dbState.UserSig, - } -} diff --git a/clearnode/store/database/contract_event_test.go b/clearnode/store/database/contract_event_test.go deleted file mode 100644 index 72e054d30..000000000 --- a/clearnode/store/database/contract_event_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package database - -import ( - "testing" - - "github.com/layer-3/nitrolite/pkg/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestStoreContractEvent(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - event := core.BlockchainEvent{ - ContractAddress: "0x1234567890123456789012345678901234567890", - BlockchainID: 1, - Name: "HomeChannelCreated", - BlockNumber: 100, - TransactionHash: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", - LogIndex: 5, - } - - err := store.StoreContractEvent(event) - require.NoError(t, err) - - // Verify the event was stored - var storedEvent ContractEvent - err = db.Where("transaction_hash = ? AND log_index = ?", event.TransactionHash, event.LogIndex).First(&storedEvent).Error - require.NoError(t, err) - - assert.Equal(t, event.ContractAddress, storedEvent.ContractAddress) - assert.Equal(t, event.BlockchainID, storedEvent.BlockchainID) - assert.Equal(t, event.Name, storedEvent.Name) - assert.Equal(t, event.BlockNumber, storedEvent.BlockNumber) - assert.Equal(t, event.TransactionHash, storedEvent.TransactionHash) - assert.Equal(t, event.LogIndex, storedEvent.LogIndex) -} - -func TestGetLatestEvent(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - contractAddress := "0x1234567890123456789012345678901234567890" - networkID := uint64(1) - - t.Run("no events returns empty event", func(t *testing.T) { - event, err := store.GetLatestEvent(contractAddress, networkID) - require.NoError(t, err) - assert.Equal(t, core.BlockchainEvent{}, event) - }) - - t.Run("returns latest event", func(t *testing.T) { - // Store multiple events - events := []core.BlockchainEvent{ - { - ContractAddress: contractAddress, - BlockchainID: networkID, - Name: "Event1", - BlockNumber: 100, - TransactionHash: "0xaaa", - LogIndex: 1, - }, - { - ContractAddress: contractAddress, - BlockchainID: networkID, - Name: "Event2", - BlockNumber: 100, - TransactionHash: "0xbbb", - LogIndex: 2, - }, - { - ContractAddress: contractAddress, - BlockchainID: networkID, - Name: "Event3", - BlockNumber: 150, - TransactionHash: "0xccc", - LogIndex: 0, - }, - } - - for _, ev := range events { - err := store.StoreContractEvent(ev) - require.NoError(t, err) - } - - // Get latest event - latestEvent, err := store.GetLatestEvent(contractAddress, networkID) - require.NoError(t, err) - - // Should return the event with highest block number - assert.Equal(t, uint64(150), latestEvent.BlockNumber) - assert.Equal(t, uint32(0), latestEvent.LogIndex) - assert.Equal(t, "Event3", latestEvent.Name) - assert.Equal(t, contractAddress, latestEvent.ContractAddress) - assert.Equal(t, networkID, latestEvent.BlockchainID) - }) - - t.Run("different contract returns empty event", func(t *testing.T) { - differentContract := "0x9999999999999999999999999999999999999999" - event, err := store.GetLatestEvent(differentContract, networkID) - require.NoError(t, err) - assert.Equal(t, core.BlockchainEvent{}, event) - }) - - t.Run("different network returns empty event", func(t *testing.T) { - differentNetwork := uint64(999) - event, err := store.GetLatestEvent(contractAddress, differentNetwork) - require.NoError(t, err) - assert.Equal(t, core.BlockchainEvent{}, event) - }) - - t.Run("returns highest log index when same block", func(t *testing.T) { - contractAddr := "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - chainID := uint64(42) - - // Store events in same block with different log indices - events := []core.BlockchainEvent{ - { - ContractAddress: contractAddr, - BlockchainID: chainID, - Name: "EventA", - BlockNumber: 200, - TransactionHash: "0x111", - LogIndex: 5, - }, - { - ContractAddress: contractAddr, - BlockchainID: chainID, - Name: "EventB", - BlockNumber: 200, - TransactionHash: "0x222", - LogIndex: 10, - }, - { - ContractAddress: contractAddr, - BlockchainID: chainID, - Name: "EventC", - BlockNumber: 200, - TransactionHash: "0x333", - LogIndex: 3, - }, - } - - for _, ev := range events { - err := store.StoreContractEvent(ev) - require.NoError(t, err) - } - - // Get latest event - should return highest log index for the block - latestEvent, err := store.GetLatestEvent(contractAddr, chainID) - require.NoError(t, err) - - assert.Equal(t, uint64(200), latestEvent.BlockNumber) - assert.Equal(t, uint32(10), latestEvent.LogIndex) - assert.Equal(t, "EventB", latestEvent.Name) - }) -} diff --git a/clearnode/store/database/db_store_test.go b/clearnode/store/database/db_store_test.go deleted file mode 100644 index e5b113d10..000000000 --- a/clearnode/store/database/db_store_test.go +++ /dev/null @@ -1,648 +0,0 @@ -package database - -import ( - "testing" - - "github.com/layer-3/nitrolite/pkg/core" - "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDBStore_GetUserBalances(t *testing.T) { - t.Run("Success - Get balances for single asset", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - - // Create state with balance - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - // Lock user state before storing (ensures row exists in user_balances) - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - balances, err := store.GetUserBalances("0xuser123") - require.NoError(t, err) - - assert.Len(t, balances, 1) - assert.Equal(t, "USDC", balances[0].Asset) - assert.Equal(t, decimal.NewFromInt(1000), balances[0].Balance) - }) - - t.Run("Success - Get balances for multiple assets", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - - // Create state for USDC - state1 := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - // Create state for ETH - state2 := core.State{ - ID: "state2", - Asset: "ETH", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(5), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - // Lock user states before storing (ensures rows exist in user_balances) - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - _, err = store.LockUserState("0xuser123", "ETH") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) - - balances, err := store.GetUserBalances("0xuser123") - require.NoError(t, err) - - assert.Len(t, balances, 2) - - // Find balances by asset - var usdcBalance, ethBalance *core.BalanceEntry - for i := range balances { - switch balances[i].Asset { - case "USDC": - usdcBalance = &balances[i] - case "ETH": - ethBalance = &balances[i] - } - } - - require.NotNil(t, usdcBalance) - require.NotNil(t, ethBalance) - assert.Equal(t, decimal.NewFromInt(1000), usdcBalance.Balance) - assert.Equal(t, decimal.NewFromInt(5), ethBalance.Balance) - }) - - t.Run("Success - Returns latest version for each asset", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - - // Create multiple versions for same asset - state1 := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - state2 := core.State{ - ID: "state2", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 2, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(2000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - // Lock user state before storing (ensures row exists in user_balances) - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) - - balances, err := store.GetUserBalances("0xuser123") - require.NoError(t, err) - - assert.Len(t, balances, 1) - assert.Equal(t, "USDC", balances[0].Asset) - assert.Equal(t, decimal.NewFromInt(2000), balances[0].Balance) // Latest version - }) - - t.Run("Success - Returns latest epoch for each asset", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - - // Create states with different epochs - state1 := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 5, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - state2 := core.State{ - ID: "state2", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 2, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{}, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(3000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - } - - // Lock user state before storing (ensures row exists in user_balances) - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) - - balances, err := store.GetUserBalances("0xuser123") - require.NoError(t, err) - - assert.Len(t, balances, 1) - assert.Equal(t, decimal.NewFromInt(3000), balances[0].Balance) // Latest epoch - }) - - t.Run("No balances found", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - balances, err := store.GetUserBalances("0xnonexistent") - require.NoError(t, err) - assert.Empty(t, balances) - }) -} - -func TestDBStore_EnsureNoOngoingStateTransitions(t *testing.T) { - t.Run("No previous state - No error", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - err := store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - require.NoError(t, err) - }) - - t.Run("HomeDeposit - Versions match", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - userSig := "0xusersig" - nodeSig := "0xnodesig" - - // Create channel - channel := core.Channel{ - ChannelID: homeChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeHome, - BlockchainID: 1, - TokenAddress: "0xtoken123", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 1, // Matches state version - } - require.NoError(t, store.CreateChannel(channel)) - - // Create signed state - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{ - Type: core.TransitionTypeHomeDeposit, - }, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - UserSig: &userSig, - NodeSig: &nodeSig, - } - - // Lock user state before storing - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - require.NoError(t, err) - }) - - t.Run("HomeDeposit - Versions mismatch", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - userSig := "0xusersig" - nodeSig := "0xnodesig" - - // Create channel with different version - channel := core.Channel{ - ChannelID: homeChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeHome, - BlockchainID: 1, - TokenAddress: "0xtoken123", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 0, // Doesn't match state version - } - require.NoError(t, store.CreateChannel(channel)) - - // Create signed state - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{ - Type: core.TransitionTypeHomeDeposit, - }, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - UserSig: &userSig, - NodeSig: &nodeSig, - } - - // Lock user state before storing - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - assert.Error(t, err) - assert.Contains(t, err.Error(), "home deposit is still ongoing") - }) - - t.Run("MutualLock - Both versions match", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - escrowChannelID := "0xescrowchannel456" - userSig := "0xusersig" - nodeSig := "0xnodesig" - - // Create home channel - homeChannel := core.Channel{ - ChannelID: homeChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeHome, - BlockchainID: 1, - TokenAddress: "0xtoken123", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 2, // Matches state version - } - require.NoError(t, store.CreateChannel(homeChannel)) - - // Create escrow channel - escrowChannel := core.Channel{ - ChannelID: escrowChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeEscrow, - BlockchainID: 137, - TokenAddress: "0xtoken456", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 2, // Matches state version - } - require.NoError(t, store.CreateChannel(escrowChannel)) - - // Create signed state - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 2, - HomeChannelID: &homeChannelID, - EscrowChannelID: &escrowChannelID, - Transition: core.Transition{ - Type: core.TransitionTypeMutualLock, - }, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - EscrowLedger: &core.Ledger{ - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - UserSig: &userSig, - NodeSig: &nodeSig, - } - - // Lock user state before storing - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - require.NoError(t, err) - }) - - t.Run("MutualLock - Home channel version mismatch", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - escrowChannelID := "0xescrowchannel456" - userSig := "0xusersig" - nodeSig := "0xnodesig" - - // Create home channel with mismatched version - homeChannel := core.Channel{ - ChannelID: homeChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeHome, - BlockchainID: 1, - TokenAddress: "0xtoken123", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 1, // Doesn't match state version - } - require.NoError(t, store.CreateChannel(homeChannel)) - - // Create escrow channel - escrowChannel := core.Channel{ - ChannelID: escrowChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeEscrow, - BlockchainID: 137, - TokenAddress: "0xtoken456", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 2, - } - require.NoError(t, store.CreateChannel(escrowChannel)) - - // Create signed state - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 2, - HomeChannelID: &homeChannelID, - EscrowChannelID: &escrowChannelID, - Transition: core.Transition{ - Type: core.TransitionTypeMutualLock, - }, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - EscrowLedger: &core.Ledger{ - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - UserSig: &userSig, - NodeSig: &nodeSig, - } - - // Lock user state before storing - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - assert.Error(t, err) - assert.Contains(t, err.Error(), "mutual lock is still ongoing") - }) - - t.Run("EscrowWithdraw - Versions match", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - escrowChannelID := "0xescrowchannel456" - userSig := "0xusersig" - nodeSig := "0xnodesig" - - // Create escrow channel - escrowChannel := core.Channel{ - ChannelID: escrowChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeEscrow, - BlockchainID: 137, - TokenAddress: "0xtoken456", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 4, // Matches state version - } - require.NoError(t, store.CreateChannel(escrowChannel)) - - // Create signed state - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 4, - HomeChannelID: &homeChannelID, - EscrowChannelID: &escrowChannelID, - Transition: core.Transition{ - Type: core.TransitionTypeEscrowWithdraw, - }, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - EscrowLedger: &core.Ledger{ - UserBalance: decimal.NewFromInt(500), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - UserSig: &userSig, - NodeSig: &nodeSig, - } - - // Lock user state before storing - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - require.NoError(t, err) - }) - - t.Run("Unsigned state - No validation", func(t *testing.T) { - db, cleanup := SetupTestDB(t) - defer cleanup() - - store := NewDBStore(db) - - homeChannelID := "0xhomechannel123" - - // Create channel with mismatched version - channel := core.Channel{ - ChannelID: homeChannelID, - UserWallet: "0xuser123", - Asset: "usdc", - Type: core.ChannelTypeHome, - BlockchainID: 1, - TokenAddress: "0xtoken123", - ChallengeDuration: 86400, - Nonce: 1, - Status: core.ChannelStatusOpen, - StateVersion: 0, - } - require.NoError(t, store.CreateChannel(channel)) - - // Create unsigned state - state := core.State{ - ID: "state1", - Asset: "USDC", - UserWallet: "0xuser123", - Epoch: 1, - Version: 1, - HomeChannelID: &homeChannelID, - Transition: core.Transition{ - Type: core.TransitionTypeHomeDeposit, - }, - HomeLedger: core.Ledger{ - UserBalance: decimal.NewFromInt(1000), - UserNetFlow: decimal.Zero, - NodeBalance: decimal.Zero, - NodeNetFlow: decimal.Zero, - }, - // No signatures - } - - // Lock user state before storing - _, err := store.LockUserState("0xuser123", "USDC") - require.NoError(t, err) - - require.NoError(t, store.StoreUserState(state)) - - // Should not error because there's no signed state - err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") - require.NoError(t, err) - }) -} diff --git a/clearnode/stress/README.md b/clearnode/stress/README.md deleted file mode 100644 index d91414fdf..000000000 --- a/clearnode/stress/README.md +++ /dev/null @@ -1,303 +0,0 @@ -# Clearnode Stress Testing Tool - -Built-in stress testing tool for validating clearnode performance, correctness, and stability under load. - -## Quick Start - -```bash -# Set target -export STRESS_WS_URL=ws://localhost:7824/ws - -# Read-only test (no wallet needed) -clearnode stress-test ping:1000:10 - -# State-mutating test (funded wallet required) -export STRESS_PRIVATE_KEY= -clearnode stress-test transfer-roundtrip:10:20:usdc -``` - -## Architecture - -The stress tool is compiled into the clearnode binary and invoked via the `stress-test` subcommand. It connects to a running clearnode instance over WebSocket using the Go SDK. - -``` -clearnode stress-test - │ - ├── Read-only methods ──► Connection pool (N parallel WebSocket clients) - │ │ - │ └── Distributes requests round-robin across connections - │ - └── State-mutating methods ──► Custom orchestration - │ - ├── transfer-roundtrip: 3-phase fund/stress/collect - └── app-session-lifecycle: create/deposit/operate/close -``` - -**Key design decisions:** -- Each WebSocket connection sends requests sequentially (waits for response before sending next) -- Parallelism is achieved through multiple connections -- Connection pool tolerates individual failures — test runs with whatever connections succeeded -- Results include per-request latency, percentile distribution, and error breakdown - -## Configuration - -All configuration is via environment variables. - -| Variable | Required | Default | Description | -|---|---|---|---| -| `STRESS_WS_URL` | Yes | - | WebSocket URL of the target clearnode | -| `STRESS_PRIVATE_KEY` | No | ephemeral | Hex-encoded ECDSA private key | -| `STRESS_CONNECTIONS` | No | `10` | Default parallel connections per test | -| `STRESS_TIMEOUT` | No | `10m` | Overall test timeout | -| `STRESS_MAX_ERROR_RATE` | No | `0.01` | Error rate threshold (0.01 = 1%) | - -When `STRESS_PRIVATE_KEY` is not set, an ephemeral key is generated. This works for read-only methods but state-mutating methods require a funded wallet. - -## Spec Format - -``` -method:total_requests[:connections[:extra_params...]] -``` - -- `method` — test method name -- `total_requests` — total number of operations to execute -- `connections` — parallel WebSocket connections (optional, falls back to `STRESS_CONNECTIONS`) -- `extra_params` — method-specific parameters (asset, amount, wallet address, etc.) - -## Available Methods - -### Read-Only Methods - -These methods test read path performance. They use a shared connection pool and do not modify server state. An ephemeral wallet is used if `STRESS_PRIVATE_KEY` is not set. - -| Method | Extra Params | Description | -|---|---|---| -| `ping` | none | WebSocket ping/pong roundtrip | -| `get-config` | none | Fetch server configuration | -| `get-blockchains` | none | List available blockchains | -| `get-assets` | `[chain_id]` | List assets, optionally filtered by chain | -| `get-balances` | `[wallet]` | Get wallet balances | -| `get-transactions` | `[wallet]` | Fetch transactions (paginated, limit 20) | -| `get-home-channel` | `asset` or `wallet:asset` | Get home channel for wallet+asset | -| `get-escrow-channel` | `channel_id` | Get escrow channel by ID | -| `get-latest-state` | `asset` or `wallet:asset` | Get latest channel state | -| `get-channel-key-states` | `[wallet]` | Get last channel key states | -| `get-app-sessions` | `[wallet]` | Query app sessions (paginated) | -| `get-app-key-states` | `[wallet]` | Get last app key states | - -**Examples:** - -```bash -# 1000 pings over 10 connections -clearnode stress-test ping:1000:10 - -# 500 config fetches over 5 connections -clearnode stress-test get-config:500:5 - -# 2000 balance queries over 20 connections -clearnode stress-test get-balances:2000:20:0x1234... - -# 1000 home channel lookups -clearnode stress-test get-home-channel:1000:10:usdc - -# Asset queries filtered by chain ID -clearnode stress-test get-assets:500:5:84532 -``` - -### State-Mutating Methods - -These methods test write path performance. They require `STRESS_PRIVATE_KEY` with a funded wallet. - -#### `transfer-roundtrip` - -Spec: `transfer-roundtrip:rounds:wallets:asset[:amount]` - -| Param | Description | -|---|---| -| `rounds` | Back-and-forth transfer rounds per wallet pair | -| `wallets` | Number of derived wallets (rounded up to even) | -| `asset` | Asset symbol (e.g., `usdc`) | -| `amount` | Transfer amount per operation (default: `0.000001`) | - -**Three-phase execution:** - -1. **Fund** — Sender distributes `amount` to each derived wallet -2. **Stress** — Wallet pairs (0,1), (2,3), ... transfer back and forth in parallel for `rounds` iterations -3. **Collect** — All wallets return funds to sender - -Wallet keys are deterministically derived from the master key using SHA-256: `masterKey:receiver:`. - -Total measured operations = `wallets * rounds` (phase 2 only). - -**Examples:** - -```bash -# 10 rounds, 20 wallets (10 pairs), usdc, default amount -clearnode stress-test transfer-roundtrip:10:20:usdc - -# 50 rounds, 100 wallets (50 pairs), custom amount -clearnode stress-test transfer-roundtrip:50:100:usdc:0.0001 -``` - -#### `app-session-lifecycle` - -Spec: `app-session-lifecycle:sessions:participants:operates:asset[:amount]` - -| Param | Description | -|---|---| -| `sessions` | Number of concurrent app session lifecycles | -| `participants` | Wallets per session (quorum = all must sign) | -| `operates` | Number of operate state updates per session | -| `asset` | Asset symbol (e.g., `usdc`) | -| `amount` | Deposit amount per session (default: `0.000003`) | - -**Per-session lifecycle:** - -1. **Create** — Create app session with all participants -2. **Deposit** — First participant deposits funds into session -3. **Operate** — Submit `N` state updates with rotating fund allocations -4. **Close** — Close session with final allocation matching last operate - -All signatures are pre-generated before the stress phase begins. Each session is driven by its first participant ("pipe lead") over a dedicated WebSocket connection. - -Wallet keys are derived using SHA-256: `masterKey:appsession::`. - -Total measured operations = `sessions * (operates + 3)`. - -**Examples:** - -```bash -# 10 sessions, 5 participants each, 3 operates per session -clearnode stress-test app-session-lifecycle:10:5:3:usdc - -# 50 sessions, 3 participants, 10 operates, custom amount -clearnode stress-test app-session-lifecycle:50:3:10:usdc:0.000005 -``` - -## Report Output - -Every test produces a standardized report: - -``` -Stress Test Report -================== -Method: ping -Total Requests: 1000 -Connections: 10 -Duration: 2.345s - -Results -------- -Successful: 998 (99.8%) -Failed: 2 (0.2%) -Requests/sec: 426.44 - -Latency -------- -Min: 1.2ms -Max: 45.3ms -Average: 2.3ms -Median (p50): 2.1ms -P95: 4.5ms -P99: 12.8ms - -Errors ------- - context deadline exceeded 2 -``` - -**Pass/fail criteria:** The test exits with code 0 (PASS) if the error rate is within `STRESS_MAX_ERROR_RATE`, or code 1 (FAIL) if exceeded. - -## Helm Integration - -The stress tool is integrated as a Helm test. When enabled, `helm test` creates a Pod that runs the stress spec against the in-cluster clearnode service. - -**values.yaml:** - -```yaml -stressTest: - enabled: true - specs: - - "ping:100000:100" - privateKey: "" # optional, for state-mutating tests - connections: 10 - timeout: "10m" - maxErrorRate: "0.01" -``` - -**Run:** - -```bash -helm test -``` - -The WebSocket URL defaults to the in-cluster service (`ws://-clearnode:7824/ws`). Override with `stressTest.wsURL` for external targets. - -## Testing Strategy - -### Phase 1: Read Path Baseline - -Validate read performance under increasing load. No funded wallet needed. - -```bash -export STRESS_WS_URL=ws://target:7824/ws - -# Baseline latency -clearnode stress-test ping:100:1 -clearnode stress-test get-config:100:1 - -# Scale connections -clearnode stress-test ping:10000:10 -clearnode stress-test ping:100000:100 -clearnode stress-test get-balances:10000:50:0xWALLET -``` - -### Phase 2: Write Path Stress - -Test state mutation throughput. Requires funded wallet. - -```bash -export STRESS_PRIVATE_KEY= - -# Small scale -clearnode stress-test transfer-roundtrip:5:4:usdc - -# Production scale -clearnode stress-test transfer-roundtrip:50:100:usdc:0.0001 -``` - -### Phase 3: App Session Lifecycle - -Test multi-participant coordination. - -```bash -# Small scale -clearnode stress-test app-session-lifecycle:5:3:3:usdc - -# Production scale -clearnode stress-test app-session-lifecycle:50:5:10:usdc:0.000005 -``` - -### Phase 4: Sustained Load - -Run extended tests to detect resource leaks and degradation. - -```bash -# High volume read -clearnode stress-test ping:1000000:100 - -# Extended write -clearnode stress-test transfer-roundtrip:500:100:usdc:0.000001 -``` - -## Troubleshooting - -| Symptom | Cause | Fix | -|---|---|---| -| `STRESS_WS_URL is required` | Missing environment variable | Set `STRESS_WS_URL` | -| `failed to open any connections` | Target unreachable or refusing connections | Verify URL and that clearnode is running | -| `WARNING: Only N/M connections established` | Server under load or connection limits | Reduce connection count or check server capacity | -| `STRESS_PRIVATE_KEY is required` | State-mutating method without key | Set `STRESS_PRIVATE_KEY` with a funded wallet | -| `fund wallet X: insufficient balance` | Sender wallet not funded | Transfer funds to the wallet address printed at startup | -| High error rate in transfer tests | Database contention or deadlocks | Check server logs for deadlock traces | -| `context deadline exceeded` | Test exceeded `STRESS_TIMEOUT` | Increase timeout or reduce test scope | diff --git a/contracts/SECURITY.md b/contracts/SECURITY.md index d9eeae45c..fe8b92719 100644 --- a/contracts/SECURITY.md +++ b/contracts/SECURITY.md @@ -2,7 +2,7 @@ ## Behavior -These are behavior rules of the Clearnode or the logic how a user (should) operate. +These are behavior rules of the Nitronode or the logic how a user (should) operate. 1. if challenged with an older state, then checkpoint with the latest one @@ -37,18 +37,21 @@ Given the 3 and 4, an invariant: Invariant: > No different states with the same `version` can exist for the same channel. -## Invariants +--- + +6. While a channel is in `CHALLENGED` (on-chain `DISPUTED`) status, the Node does not co-sign "receive" states for that channel. Incoming off-chain transfer credits and app-session release credits targeting the channel are appended as unsigned entries to the channel's state history (a per-channel queue). On challenge clearance, the off-chain head is signed by the Node and offered for user acknowledgement. On channel closure, a single `challenge_rescue` state is committed to the user's next epoch detached from the closed channel. Its amount is the net effect on the home-channel balance of transitions stored strictly above the closure version: receives (`transfer_receive`, `release`) contribute positively; sends (`transfer_send`, `commit`) contribute negatively; other transition kinds are excluded because they require onchain backing the chain did not enforce. Signed pre-challenge and unsigned during-challenge rows both contribute. The result is clamped at zero so an adversarial close at a version where the user's own balance was higher than the off-chain head cannot dock the user further; when no relevant transitions exist above closure, the rescue carries zero credit and serves only to advance the state chain off the closed channel. + +Invariant: +> A channel in `CHALLENGED` status has no co-signed "receive" states; therefore, its dispute cannot be cleared by enforcing a receive-credit checkpoint. --- -- (NOT TRUE) only less-or-equal amount of internally-accounted funds can be withdrawn (NOT TRUE for states that include "receive" off-chain ops) +7. While a channel is in `CHALLENGED` status, all user-initiated operations on the channel are blocked except receiving funds (which the Node queues per rule 6). This includes opening a new channel on the same chain for the same asset. These restrictions persist after the challenge timer expires, until the channel is explicitly closed and `ChannelClosed` event is seen by the Node. -The absence of the aforementioned invariant creates a huge risk of an attacker draining the Node. -To protect from this, the Node should keep CORRECT track of off-chain user funds. -CAUTION IS REQUIRED. +Invariant: +> Credits accrued while a channel was in `CHALLENGED` status are preserved — either reintegrated into the channel on challenge clearance, or carried into the next epoch on closure — and cannot be shadowed by a concurrently-opened replacement channel. -P.S. This invariant still can be enforced by updating `lockedFunds` per channel meta-variable during on-chain state processing, -e.g. when processing "receive X, withdraw Y", increase `lockedFunds` (and "lock" Node's funds in channel) by X, and then decrease by Y. +## Invariants --- @@ -69,8 +72,8 @@ e.g. when processing "receive X, withdraw Y", increase `lockedFunds` (and "lock" 1. **Channel uniqueness**: A channel identified by `channelId = hash(Definition)` can be created at most once. 2. **Cross-deployment replay protection**: Each ChannelHub deployment has a `VERSION` constant (currently 1). The version is encoded as the first byte of `channelId = setFirstByte(hash(Definition), VERSION)`, ensuring that the same channel definition produces different `channelId` values across different ChannelHub versions. This prevents signature replay attacks across different ChannelHub deployments on the same chain. Only one ChannelHub deployment per version per chain is intended. The `escrowId = hash(channelId, stateVersion)` inherits this protection. 3. **Signature authorization**: Every enforceable state must be signed by both User and Node (unless explicitly relaxed in future versions). -4. **Pluggable signature validation**: Signature validation is performed by validator contracts implementing the `ISignatureValidator` interface. The ChannelHub has a `defaultSigValidator` (0x00), and nodes maintain a registry of validators (0x01-0xFF). The first byte of each signature determines which validator is used: `0x00` for default, `0x01-0xFF` for node-registered validators. -5. **Validator security requirements**: Signature validators must be trustworthy, gas-efficient, and correctly implement validation logic. A compromised or buggy validator can break authorization for all channels using that validator. Validators should be immutable or have strict upgrade controls. Nodes are responsible for registering only trusted validators in their registry. +4. **Pluggable signature validation**: Signature validation is performed by validator contracts implementing the `ISignatureValidator` interface. The ChannelHub has a `defaultSigValidator` (0x00), and NODE maintains a registry of validators (0x01-0xFF). The first byte of each signature determines which validator is used: `0x00` for default, `0x01-0xFF` for NODE-registered validators. +5. **Validator security requirements**: Signature validators must be trustworthy, gas-efficient, and correctly implement validation logic. A compromised or buggy validator can break authorization for all channels using that validator. Validators should be immutable or have strict upgrade controls. NODE is responsible for registering only trusted validators in its registry. 6. **Version monotonicity**: For a given channel, every valid state has a strictly increasing `version`. 7. **Version uniqueness**: No two different states with the same `version` may exist for the same channel. @@ -108,6 +111,7 @@ e.g. when processing "receive X, withdraw Y", increase `lockedFunds` (and "lock" 17. **Latest-state challenge rule**: A challenge must reference a state with `version ≥ lastEnforcedVersion`; if higher, that state is enforced first. 18. **Challenge resolution**: Any strictly newer valid state supersedes an active challenge and returns the channel to `OPERATING`. 19. **Challenge finality**: If no newer state is enforced before challenge expiry, the channel may be unilaterally closed using the last enforced state. +20. **INITIATE_ESCROW_DEPOSIT home-chain caller restriction**: `INITIATE_ESCROW_DEPOSIT` on the home chain may only be submitted by the Node, via either `initiateEscrowDeposit` or `challengeChannel`. This invariant holds despite the general principle that any party may enforce any valid signed state: the home-chain path of `INITIATE_ESCROW_DEPOSIT` exclusively adjusts Node allocations (`userNfDelta == 0`, `userFundsDelta == 0`), so it removes no user right. The restriction closes a DDoS vector where an attacker with zero capital could lock arbitrary Node liquidity by submitting signed initiation states directly on the home chain without first locking funds on the non-home chain. A user who needs to dispute a channel whose latest on-chain state is `INITIATE_ESCROW_DEPOSIT` can challenge with the immediate predecessor: because the user's allocation is unchanged across that transition, the fund distribution is identical. --- @@ -119,6 +123,60 @@ e.g. when processing "receive X, withdraw Y", increase `lockedFunds` (and "lock" --- +## Cross-chain Operation Ordering + +### Invariant 21 is an off-chain, not an on-chain, guarantee + +Invariant 21 states that the Node must not issue new states during an in-progress escrow or migration. +This ordering constraint **cannot be fully enforced by the on-chain contract** for a fundamental +reason: a contract on chain B has no visibility into what is happening on chain A. It cannot query +whether an escrow is `INITIALIZED` on the other chain, whether a migration is halfway through, or +whether any other channel operation is pending. + +### Why a flag-based on-chain guard is insufficient + +An `actionStarted` flag per channel could block new operations while a cross-chain one is in +progress, but it would be asymmetric: + +- **Escrow withdrawal**: flag can be raised on initiate and lowered on finalize — both happen on the + non-home chain, so the full lifecycle is visible. +- **Escrow deposit**: flag raised on initiate (non-home chain), but the protocol-level finalization + that matters (user allocation credited) happens on the home chain. The non-home chain only sees + the node reclaiming locked funds, which is a different event. +- **Migration**: flag raised on initiate (non-home chain, `MIGRATING_IN`), but finalization happens + on the old home chain, advancing the channel to `MIGRATED_OUT`. + +Applying the flag consistently across all three would require per-channel storage tracking active +operations, cross-operation coordination logic, and would still not close the gap for deposit and +migration, where the terminal event is on a different chain. + +### Design consequence: the on-chain contract must handle concurrent operations gracefully + +Because on-chain enforcement of ordering is incomplete, the contract is designed so that any +reachable state sequence produces a correct outcome, even if the off-chain ordering invariant was +violated. Version monotonicity is enforced independently on each chain: each contract tracks only +the version of the last state it enforced locally, with no cross-chain synchronization of version +counters. The canonical example is: + +1. Escrow withdrawal initiated on chain B (non-home) — `EscrowWithdrawalMeta` created, node funds + locked. +2. Migration initiated on chain B — channel becomes `MIGRATING_IN`, chain B is now treated as home. +3. Escrow withdrawal finalized on chain B — routed via metadata presence + (`_isEscrowWithdrawalHomeChain`), not via the mutable `_isChannelHomeChain` result, so the + non-home path remains reachable and funds are correctly released to the user. + +This flow is reachable on-chain only with submission order X → Y → X+1 (X = initiate escrow, +X+1 = finalize escrow, Y = initiate migration, Y > X+1); the signing order must still be +monotonically increasing — X+1 pre-signed as the execution phase before Y is signed — so only +rule 21 is broken. If the signing order were also X → Y → X+1, the on-chain contract would +behave identically; that would only add a violation of the version-monotonicity signing rule +with no additional on-chain effect. + +Under correct Node behavior this sequence never occurs. But the contract handles it safely so that +no funds can be permanently locked if it does. + +--- + ### Safety guarantees 23. **Enforcement determinism**: Enforcing the same `(prevState, candidateState)` pair always yields the same on-chain result. @@ -127,6 +185,58 @@ e.g. when processing "receive X, withdraw Y", increase `lockedFunds` (and "lock" --- +### ChannelClosed event orientation during abandoned migration + +`initiateMigration()` on the new home chain swaps `homeLedger` ↔ `nonHomeLedger` before storing the state, so that `homeLedger` always represents the chain where execution happens. A consequence of this swap is that `meta.lastState` on the new home chain is stored in opposite orientation from what both parties signed. + +If a migration is initiated but never finalized and both channel copies are subsequently challenged and closed after timeout, `ChannelClosed` can be emitted on **both chains** for the same `channelId` and state version, but with **opposite `homeLedger`/`nonHomeLedger` orientation** in `finalState`: + +- **Old home chain** (`OPERATING`/`DISPUTED`): `finalState.homeLedger` describes the old home chain (original orientation as signed). +- **New home chain** (`MIGRATING_IN`/`DISPUTED`): `finalState.homeLedger` describes the new home chain (swapped orientation as stored). + +On-chain accounting is correct in both cases — each chain pays from its own locally stored allocations. The concern is for **off-chain consumers** of the `ChannelClosed` event: + +- Key `ChannelClosed` events by `(chainId, ChannelHub, channelId)`, never by `channelId` alone. +- Treat each emission as a distinct local settlement; there is no single canonical `finalState` for an abandoned migration close. +- Code that persists the emitted `finalState` must handle the swapped ledger orientation for channels in `MIGRATING_IN` status at close time. + +Under correct Node behavior this scenario does not occur: the Node stops issuing new states during migration and ensures either finalization or reversion before closing. The on-chain contract handles it safely so that no funds are permanently locked if it does occur. + +--- + +## Trust Assumptions + +Beyond the cryptographic and on-chain guarantees listed above, correct user fund protection depends on the following trust assumptions about Node behavior. + +### Node as off-chain transfer intermediary + +When a user sends funds off-chain to another party, the protocol requires two independent state updates that the Node must countersign: + +1. The sender's channel state: user allocation decreases, encoding the transfer. +2. The receiver's channel state: receiver allocation increases. + +A malicious Node could countersign the sender's state (making the reduction in user allocation on-chain enforceable) while withholding the receiver's credit state — pocketing the transferred funds. The on-chain contract has no visibility into independent per-channel state updates and cannot enforce atomicity between them. + +**Users must trust the Node to faithfully execute both legs of every off-chain transfer.** This extends the existing Node trust relationship (liquidity management, cross-chain relay) to include off-chain transfer routing. If the Node behaves maliciously, the user's recourse is to challenge the channel and close it, recovering whatever funds remain in their on-chain allocation at the last enforced state. + +### Node off-chain credit accounting + +The protocol does not bound on-chain withdrawals to the amount explicitly deposited. States that include "receive" off-chain operations can increase a user's allocation beyond what was deposited, reflecting credit extended by the Node. A Node that over-issues credits may have insufficient vault funds to honour those allocations when they are enforced on-chain. + +**Users must trust the Node to extend only credit that is backed by actual vault funds.** The Node is solely responsible for maintaining correct off-chain accounting of all user balances and ensuring no signed state represents an allocation the Node cannot fund. + +### Signature validator selection + +In the single-node deployment model, the ChannelHub is deployed by the Node operator, who also chooses the `defaultSigValidator`. The following trust properties apply: + +- **Default validator trust**: All participants using the default validator (0x00) trust the ChannelHub deployer's choice of default validator. +- **User validator control**: Users control which additional validators (beyond the always-available default) can verify their signatures via the `approvedSignatureValidators` bitmask in `ChannelDefinition`. This prevents nodes from forging user signatures by registering malicious validators. +- **Validator agreement**: Both users and nodes can only use agreed validators specified in the bitmask (plus the always-available default validator), preventing unilateral changes to signature validation schemes. +- **Registration immutability**: Once a node registers a validator at a specific ID, it cannot be changed. Signatures created with a given validator ID remain valid for the lifetime of the ChannelHub deployment. +- **Cross-chain consistency**: The same validator ID may map to different validator addresses on different chains, but the security properties must remain equivalent. Nodes are responsible for registering compatible validators across chains. + +--- + ## Signature Validation Security The Nitrolite protocol uses a pluggable signature validation system to support flexible authorization schemes. This section describes the security model and considerations for signature validators. @@ -141,22 +251,22 @@ The protocol uses two mechanisms for validator selection to prevent signature fo - The default ECDSA validator (0x00) is **always** available, regardless of the bitmask value - The bitmask specifies additional validators from the node's registry that are agreed validators (e.g., if bit 42 is 1, validator ID 42 is approved) - Since `approvedSignatureValidators` is part of `channelId` computation, agreed validators cannot be changed during cross-chain operations without invalidating signatures -- This prevents malicious nodes from forging user signatures by controlling validator selection +- This prevents malicious nodes from forging user signatures on **already-created channels** (where `channelId` embeds the agreed bitmask); it does **not** protect `createChannel` or `closeChannel`, where the `ChannelDefinition` — including its bitmask — arrives in calldata and there is no prior signed state to bind it **Node validator registry:** -- Nodes register signature validators and assign them 1-byte identifiers (0x01-0xFF) -- Both users and nodes can only use agreed validators (from the bitmask) or the default validator +- NODE registers signature validators and assigns them 1-byte identifiers (0x01-0xFF) +- Both users and NODE can only use agreed validators (from the bitmask) or the default validator - The first byte of each signature determines which validator is used for verification **Validator selection:** - **Default validator** (0x00): The ChannelHub is initialized with a `defaultSigValidator` address that implements `ISignatureValidator`. This validator is used when the signature's first byte is `0x00`. **Always available**, regardless of `approvedSignatureValidators` bitmask. -- **Node-registered validators** (0x01-0xFF): Nodes register validators on-chain with unique IDs. Only available if the corresponding bit is set in `ChannelDefinition.approvedSignatureValidators` (e.g., bit 42 set = validator ID 42 approved). +- **NODE-registered validators** (0x01-0xFF): NODE registers validators on-chain with unique IDs. Only available if the corresponding bit is set in `ChannelDefinition.approvedSignatureValidators` (e.g., bit 42 set = validator ID 42 approved). **Registration security:** -- Nodes register validators by signing `abi.encode(validatorId, validatorAddress, block.chainid)` off-chain +- NODE registers validators by signing `abi.encode(validatorId, validatorAddress, block.chainid)` off-chain - The signature includes `block.chainid` for cross-chain replay protection (chain-specific registrations) - Anyone can relay the registration transaction (relayer-friendly) - Registration uses ECDSA recovery (EIP-191 with raw ECDSA fallback) @@ -176,23 +286,94 @@ The protocol maintains clear separation between protocol concerns (ChannelHub) a - Automatically tries EIP-191 (with Ethereum prefix) and raw ECDSA - 65-byte signatures: `[r: 32 bytes][s: 32 bytes][v: 1 byte]` - Recommended for all users and nodes + - `validateChallengeSignature`: appends `"challenge"` suffix to the signing data 2. **SessionKeyValidator** (`src/sigValidators/SessionKeyValidator.sol`) - Session key delegation with metadata - Enables temporary signing authority (hot wallets, time-limited access) - Two-level validation: participant authorizes session key, session key signs state - - **Safe for user usage** (with Clearnode validation) - - **NOT safe for node usage** (no user-side validation) - see SessionKeyValidator Security Considerations below + - **Safe for user usage** (with Nitronode validation) + - **NOT safe for node usage** (no user-side validation) — see SessionKeyValidator Security Considerations below + - `validateChallengeSignature`: **not supported** — always reverts with `ChallengeWithSessionKeyNotSupported` See `signature-validators.md` for detailed documentation on each validator. -### Trust Model +### On-Chain vs Off-Chain Signature Domain Asymmetry -- **Default validator trust**: All participants using the default validator (0x00) trust the ChannelHub deployer's choice of default validator. -- **User validator control**: Users control which additional validators (beyond the always-available default) can verify signatures via the `approvedSignatureValidators` bitmask in `ChannelDefinition`. This prevents nodes from forging user signatures by registering malicious validators. Users can approve specific validators from the node's registry by setting the corresponding bits. -- **Validator agreement**: Both users and nodes can only use agreed validators specified in the bitmask (plus the always-available default validator). This ensures that validators are mutually agreed upon and prevents unilateral changes to signature validation schemes. -- **Registration immutability**: Once a node registers a validator at a specific ID, it cannot be changed. This ensures that signatures created with a given validator ID remain valid for the lifetime of the ChannelHub deployment. -- **Cross-chain consistency**: The same validator ID may map to different validator addresses on different chains, but the security properties must remain equivalent. Nodes are responsible for registering compatible validators across chains. +The default ECDSA validator (`EcdsaSignatureUtils.validateEcdsaSigner`) accepts **both EIP-191 and raw ECDSA** signatures on-chain: it first attempts EIP-191 recovery (the `"\x19Ethereum Signed Message:\n"` prefix used by `eth_sign`), then falls back to raw `keccak256(message)` recovery if EIP-191 fails. + +The Nitronode off-chain validator (`ChannelSigValidator`) uses **EIP-191 only** — no raw-ECDSA fallback. + +**Consequence:** A channel state signature produced with raw `keccak256` (no EIP-191 prefix) will pass on-chain validation but be rejected off-chain by the Nitronode. Under correct protocol operation this cannot occur, because the Nitronode only countersigns states it has already verified off-chain. However, implementations building custom signers or relayers must be aware of this asymmetry. + +**All client implementations must use EIP-191 (`eth_sign`) for channel state signatures** to ensure round-trip validity on both the off-chain (Nitronode) and on-chain (ChannelHub) validation paths. The raw-ECDSA fallback exists in the on-chain contract as a compatibility measure and should not be relied upon for new integrations. + +### Bootstrap vulnerability: initial user signature at `createChannel` + +> Full analysis with all considered options and trade-offs: [`initial-user-sig-validation.md`](initial-user-sig-validation.md). + +#### Root cause + +The `approvedSignatureValidators` bitmask protects user signatures on existing channels because the bitmask is embedded in `channelId`, which every prior state already covers. At `createChannel` time there is no prior state — the `ChannelDefinition` (and therefore the bitmask) arrives in calldata from the transaction sender. + +This creates a circular dependency: + +```txt +approvedSignatureValidators (attacker-controlled calldata) + → selects which validator verifies user's consent + → verifies user's "approval" of approvedSignatureValidators +``` + +**Attack**: A node (basically, any address can be a node) registers a malicious `ISignatureValidator` that always returns `VALIDATION_SUCCESS`, then calls `createChannel` with a `ChannelDefinition` that sets the bitmask to include that validator. The channel is created without the user's knowledge. The same bypass applies to `closeChannel`, allowing the node to push locked funds to itself. + +The `approvedSignatureValidators`-in-`channelId` protection prevents retroactive validator swapping on *existing* channels but does nothing to prevent a node from crafting a fresh `ChannelDefinition` for a brand-new channel, because there is no pre-existing signed state to protect. + +#### Current mitigation: per-node ChannelHub deployment + +Each ChannelHub is constructed with an immutable and trusted `NODE` address. `_requireValidDefinition` enforces `def.node == NODE`, rejecting any channel creation attempt that references a different (non-trusted) node. + +**Security properties of this mitigation:** + +- Attacks by any address are structurally impossible: a hacker cannot open channels on a ChannelHub bound to a different node, so users of deployment A are fully isolated from the node operating deployment B. +- The attack surface is reduced to the single bound node. Users who interact with a deployment already trust that node (they sign off-chain states with it and grant it ERC20 allowances); the forgery capability sits within that existing trust boundary. +- No governance, no admin key, no multisig required. + +**Validator activation delay (`VALIDATOR_ACTIVATION_DELAY = 1 day`):** + +A newly registered validator cannot be used until `registeredAt + VALIDATOR_ACTIVATION_DELAY` has elapsed. This adds a partial, targeted defence against draining user ERC20 approvals via fake `createChannel(DEPOSIT)`. + +The registration is an observable on-chain event, and with monitoring in place, the node operator can detect a compromise and alert users to revoke ERC20 approvals before the delay expires. Without the delay, registration and exploitation can occur in the same block with no response possible. + +**Residual risk:** After the activation delay, the bound node can still exploit the vulnerability. This risk is accepted under the per-node deployment trust model. + +**Operational consequence:** Each node requires its own ChannelHub deployment and its own set of ERC20 approvals from users. A single deployment cannot serve multiple independent nodes. Validators must be registered 1 day before first use (one-time cost per validator). + +**User responsibilities and monitoring requirement:** + +The `VALIDATOR_ACTIVATION_DELAY` is only effective if users actively monitor on-chain validator registrations within the 1-day window. The delay provides no protection for users who are not watching. Key points every user must understand: + +1. **Validators are permanent.** Once a validator is registered at a given ID, it cannot be deactivated or overwritten. If a malicious validator becomes active, it remains active for the lifetime of the ChannelHub deployment. There is no on-chain recovery mechanism after the delay expires. + +2. **The 1-day window is the entire response budget.** After `VALIDATOR_ACTIVATION_DELAY` elapses, the validator is active and any outstanding ERC20 approvals to ChannelHub are immediately exploitable via a forged `createChannel`. + +3. **Users must subscribe to `ValidatorRegistered` events.** Monitor the `ValidatorRegistered(uint8 indexed validatorId, address indexed validator)` event on the ChannelHub contract. Any unexpected registration should be treated as a potential compromise. Upon detecting an unrecognised validator: + - Revoke all ERC20 approvals granted to ChannelHub immediately (protects undeposited funds). + - Exit any open escrow positions via `initiateEscrowWithdrawal` before the delay expires — funds already in escrow remain exposed until withdrawn, since a malicious validator can forge user signatures for any on-chain operation, not only `createChannel`. + +4. **Avoid large standing ERC20 approvals.** Do not grant unlimited (`type(uint256).max`) or long-lived ERC20 allowances to ChannelHub. Prefer exact-amount approvals per operation. A standing approval only becomes exploitable once a malicious validator activates, so minimising the approved amount caps the worst-case loss. + +The Go SDK exposes `Client.WatchValidatorRegistered` and the TypeScript SDK exposes `Client.watchValidatorRegistered`, both delivering events as streams. The two implementations differ in transport requirements: the Go watcher uses `SubscribeFilterLogs` and requires a WebSocket or IPC endpoint (`wss://`); the TypeScript watcher uses viem's `watchContractEvent` which falls back to polling `getLogs` over HTTP when no WebSocket endpoint is configured. + +#### Stronger alternatives + +**Option F — Protocol-managed bootstrap registry.** A separate registry controlled by a `bootstrapAdmin` multisig lists the validators permitted for `createChannel` user-sig validation. Nodes have no influence over this registry. New schemes (e.g. an ERC-4337 freezer validator) can be added without redeployment. The remaining attack requires compromising the multisig; using a timelock gives users a guaranteed observation window. Supports multiple nodes in one deployment. + +**Option G — Two-registry system with tiered trusted validators.** The trusted validator set is split into a hardcoded tier (IDs 0–2, immutable in bytecode) and a governance tier (IDs 3+, multisig-extensible with a contract-enforced activation delay). `createChannel` accepts **only hardcoded-tier IDs** for user-sig validation; no governance action can influence it. Subsequent operations accept both tiers, gated by the bitmask stored at creation time. Properties: + +- `createChannel` is fully admin-proof: no governance compromise can affect bootstrap validation. +- Existing channels are bitmask-isolated: a newly added (even malicious) governance-tier validator cannot be used on channels that did not opt in at creation time. +- Future wallet formats (ERC-4337, etc.) are supported incrementally via governance without redeployment. +- Supports multiple nodes in one deployment. --- @@ -204,13 +385,13 @@ See `signature-validators.md` for detailed documentation on each validator. SessionKeyValidator enables delegation of signing authority to temporary session keys. The session key is authorized by a participant's signature, and metadata (expiration, scope, permissions) is hashed and included in the authorization. -**Key architectural decision**: Metadata validation is performed **off-chain** by the Clearnode, not on-chain. The smart contract only validates cryptographic signatures, not the semantic meaning of the metadata. +**Key architectural decision**: Metadata validation is performed **off-chain** by the Nitronode, not on-chain. The smart contract only validates cryptographic signatures, not the semantic meaning of the metadata. #### User Usage (Safe) When a **user** employs SessionKeyValidator: -1. **Off-chain enforcement layer**: The Clearnode (node software) retrieves and validates session key metadata +1. **Off-chain enforcement layer**: The Nitronode (node software) retrieves and validates session key metadata - Checks expiration timestamps - Enforces allowed channel IDs - Validates operation permissions @@ -221,8 +402,8 @@ When a **user** employs SessionKeyValidator: - Node rejects suspicious or invalid activity 3. **Limited blast radius**: If a user's session key is compromised: - - Expired keys are rejected by Clearnode - - Out-of-scope operations are rejected by Clearnode + - Expired keys are rejected by Nitronode + - Out-of-scope operations are rejected by Nitronode - Node refuses to countersign - Channel can be challenged and closed - User's main key remains secure @@ -236,7 +417,7 @@ When a **user** employs SessionKeyValidator: When a **node** employs SessionKeyValidator (NOT RECOMMENDED): -1. **No off-chain enforcement**: The user has no equivalent to Clearnode +1. **No off-chain enforcement**: The user has no equivalent to Nitronode - User cannot decode or validate node's session key metadata - No user-side software validates expiration or scope @@ -249,7 +430,79 @@ When a **node** employs SessionKeyValidator (NOT RECOMMENDED): - Session key has full node authority - User has no protection against misuse -4. **Asymmetric security**: User-side session keys are safe (Clearnode validates), node-side session keys are unsafe (no user-side validator) +4. **Asymmetric security**: User-side session keys are safe (Nitronode validates), node-side session keys are unsafe (no user-side validator) + +#### Challenge Restriction + +Session keys cannot be used for challenge signatures. `SessionKeyValidator.validateChallengeSignature` always reverts with `ChallengeWithSessionKeyNotSupported`. + +**Rationale**: A session key authorization — once signed by the user — is permanently valid on-chain because the contract only checks the cryptographic signature, not expiration or revocation. If session keys were allowed to challenge, an expired or revoked key could put any channel (where the validator is approved) into `DISPUTED` state unilaterally, bypassing Nitronode's off-chain enforcement and causing a DoS on the channel. + +#### Accepted Limitation: Off-Chain Session Key Scope Enforcement Does Not Apply to Direct Receive-State Acknowledgement + +Session key expiration and asset-scope restrictions are enforced **off-chain** by the Nitronode only. The `SessionKeyValidator` contract validates cryptographic signatures alone — it has no on-chain representation of expiration timestamps or allowed assets. + +**Receive-state background**: When a user receives funds off-chain, the node produces a state reflecting the increased allocation and signs it unilaterally. This node-only-signed state must be *acknowledged* by the user — signed by the user's key — to become mutually signed and enforceable on-chain. The Nitronode's `acknowledge` endpoint enforces session key validity (expiration, scope) before accepting the acknowledgement. + +**The bypass**: A user — or any third party in possession of the session key, including keys that have expired, been revoked off-chain, or explicitly retired — can skip the `acknowledge` endpoint entirely by: + +1. Fetching the node-signed receive state directly. +2. Signing it manually with the session key. +3. Submitting the mutually-signed state directly to the ChannelHub contract. + +The contract accepts the submission because both signatures are cryptographically valid. The on-chain contract has no mechanism to enforce off-chain session key constraints. + +**Accepted risk**: This inconsistency is acknowledged and accepted. Receive states can only increase the user's allocation — they cannot redirect funds away from the user. Earlier on-chain enforcement of receive states is always in the user's interest: if the node becomes unavailable before pending receive states are acknowledged, submitting them directly on-chain is the only remaining recovery path. Because the outcome is strictly beneficial to the user, this bypass does not constitute a financial risk. + +**User guidance**: Any party in possession of a session key that was ever authorized on a channel — regardless of whether the key has since expired, been revoked, or been retired — can acknowledge pending receive states and enforce them on-chain. Users should treat session key material accordingly even after decommissioning a key. + +--- + +## Unsupported Token Types + +Only standard ERC20 tokens and native ETH are supported. The following token types are incompatible with the static ledger model (`_nodeBalances`, `lockedFunds`) that only updates on explicit deposit and withdrawal events. There is no hard-coded guardrail preventing deposit of these tokens — the contract will accept them, but any discrepancy will produce undefined accounting behavior for all users of that token. Enforcement is off-chain: the Node will not sign states that reference unsupported token types. + +### Rebasing tokens + +Rebasing tokens (e.g. stETH, aTokens, rebase stablecoins) are **not supported**. When a rebasing token adjusts balances autonomously, the ledger permanently diverges from the actual contract balance. A negative rebase creates an insolvency condition: the ledger overstates holdings, so late withdrawers may receive less than recorded or nothing at all, and any deferred reclaim obligations become unfulfillable. + +Use non-rebasing equivalents where available (e.g. wstETH instead of stETH). + +### Fee-on-transfer tokens + +Fee-on-transfer tokens are **not supported**. The amount received by the contract is less than the amount recorded in the ledger, causing it to overstate holdings from the very first deposit. This produces the same class of insolvency as a negative rebase: late withdrawers may receive less than recorded or nothing at all. + +### Tokens without `decimals()` + +ERC-20 defines `decimals()` as an **optional** extension (via `IERC20Metadata`), not a core requirement. However, during token decimals validation a `IERC20Metadata(token).decimals()` is called, which will revert the outer transaction if the call fails. This check runs on every channel state transition, escrow deposit, and escrow withdrawal — meaning tokens that omit `decimals()` **cannot be used at all**, even for otherwise supported operations. + +Tokens must implement `IERC20Metadata.decimals()`. Unlike the rebasing and fee-on-transfer restrictions (which are accounting-correctness requirements), this is a hard on-chain gate: the contract will reject the transaction immediately rather than silently misaccount. + +--- + +## Native ETH vs ERC20 Deposit Asymmetry + +When pulling funds from a user, ERC20 and native ETH behave differently: + +- **ERC20**: Funds are pulled via `transferFrom` using a prior user allowance. Any caller can submit a signed state that triggers a deposit — the funds come from the user's approval. + +- **Native ETH**: The caller must attach the exact `msg.value`. Whoever submits the transaction must supply the ETH, regardless of who the logical depositor is. + +### Affected operations + +This asymmetry applies to every operation that pulls funds from the user: + +| Function | Context | +|----------|---------| +| `createChannel` | Initial deposit on channel creation (`DEPOSIT` intent) | +| `depositToChannel` | Channel deposit | +| `initiateEscrowDeposit` | Escrow deposit initiation (non-home chain) | + +### Practical consequence + +For ERC20 channels, any party holding a valid signed state that requires a user deposit can submit it on-chain, and the user's pre-approved funds are pulled automatically. For native ETH channels, only a caller willing to supply the required `msg.value` can submit such a state. In practice, this means native ETH deposit states must be submitted by the user themselves (or by a party willing to front the ETH on their behalf). + +Integrators building relayers or third-party submission flows should account for this difference: ERC20 state submissions are permissionless given prior user approval, while native ETH state submissions that require user funds are not. --- @@ -277,7 +530,7 @@ Inbound transfer failures occur during: - Channel deposits (DEPOSIT intent) - Escrow deposit initiation (INITIATE_ESCROW_DEPOSIT on non-home chain) -**Mitigation**: The Clearnode only processes operations after observing successful on-chain events. If a user signs a deposit state but the transfer fails on-chain, the state is never enforced, and the Node does not provide services based on unconfirmed deposits. +**Mitigation**: The Nitronode only processes operations after observing successful on-chain events. If a user signs a deposit state but the transfer fails on-chain, the state is never enforced, and the Node does not provide services based on unconfirmed deposits. --- @@ -351,7 +604,9 @@ When a transfer consumes all available gas, the transaction reverts, enabling: **ERC20 tokens with hooks**: -- **ERC777** (most dangerous): Executes `tokensReceived()` hook on recipient even for standard `transfer()` calls +- **ERC777** (most dangerous): Executes `tokensReceived()` hook on recipient even for standard `transfer()` calls. This creates two distinct attack vectors: + 1. **Gas depletion**: hook consumes all forwarded gas, causing the transaction to revert + 2. **Donation-back double-spend**: hook sends tokens back to ChannelHub during the transfer, increasing ChannelHub's balance above `balanceBefore - amount`. A balance-delta success check would misidentify this as a failed transfer and incorrectly credit `_reclaims`, letting the recipient claim the same amount twice. The protocol therefore uses **return-value checking** (not balance-delta checking) to detect ERC20 transfer success, matching `SafeERC20.trySafeTransfer` semantics with a gas cap. - **ERC1363/ERC677** (lower risk): Include `transferAndCall()` methods that trigger recipient hooks **Why it matters**: Even if protocol primarily supports standard ERC20, human error can introduce vulnerable tokens: @@ -372,3 +627,59 @@ When a transfer consumes all available gas, the transaction reverts, enabling: **Combined with reclaim pattern**: Gas limiting prevents depletion attacks; reclaim pattern handles all other failure modes (blacklists, paused tokens). Both protections are essential. --- + +## Escrow Deposit Purge Queue + +### Overview + +The contract maintains a FIFO queue of escrow deposit IDs (`_escrowDepositIds`), sorted by `unlockAt` ascending, with a monotonically advancing head pointer (`escrowHead`). After an escrow deposit's challenge period expires without resolution, the node's locked funds are returned to the node vault during a purge pass. Every protocol operation automatically calls `_purgeEscrowDeposits(MAX_DEPOSIT_ESCROW_STEPS)` to advance the queue; the public `purgeEscrowDeposits(maxSteps)` function allows any external caller to drain accumulated backlog. + +### DoS via unbounded iteration + +Without a step cap, an adversary could create many escrow deposits, allow them to accumulate without finalization, and cause every subsequent protocol operation to exhaust block gas iterating over the backlog. + +`maxSteps` caps the number of entries **inspected**, not the number **purged**. Every loop iteration — skip over FINALIZED, skip over DISPUTED, successful purge, or halt on not-yet-unlockable — consumes one step from the budget. If only successful purges counted, an attacker could pad the queue prefix with FINALIZED entries (by completing many cheap escrows) to make the loop exhaust its entire budget on no-op skips before ever reaching purgeable entries, defeating the mechanism entirely. + +### Entry disposition + +| Status | Action | Head advances | Step consumed | +|--------|--------|:---:|:---:| +| FINALIZED | Skipped | Yes | Yes | +| DISPUTED (challenge active) | Skipped | Yes | Yes | +| INITIALIZED, `unlockAt ≤ now` | Purged — locked amount credited to node vault | Yes | Yes | +| INITIALIZED, `unlockAt > now` | Scan stops | No | Yes | + +The scan halts on the first not-yet-unlockable INITIALIZED entry. Because the queue is sorted by `unlockAt` ascending, no entry deeper in the queue can have an earlier expiry, so continuing would yield no purges and only waste gas. + +### DISPUTED entries: skipped but not purged + +A DISPUTED entry has an active challenge and its locked funds are still contested — they cannot be unconditionally returned to the node. The purge skips such entries and advances the head past them so that purgeable entries later in the queue are not permanently blocked by an unresolved dispute. + +Critically, DISPUTED entries still consume a step. Without this, an actor could keep many escrow deposits in DISPUTED state indefinitely (by repeatedly challenging before the unlock window closes) to pad the queue prefix with cheap-to-produce DISPUTED entries, suppressing purge progress for other nodes' entries within the fixed step budget. + +### Formal invariant + +> **Bounded purge iteration** (complements invariant 22): `_purgeEscrowDeposits(maxSteps)` inspects at most `maxSteps` queue entries per call. Every inspected entry, regardless of disposition (skipped, purged, or halting), counts against the budget. The per-operation automatic budget is `MAX_DEPOSIT_ESCROW_STEPS = 64`. + +--- + +## Informational Events + +Several `ChannelHub` events are **informational** — they are emitted on a best-effort basis when a specific dedicated function path is taken, but are not guaranteed to fire for every logical occurrence of the operation they name. A newer signed state that supersedes an intermediate cross-chain step can be enforced directly through a standard channel operation (deposit, withdraw, checkpoint, challenge), bypassing the dedicated function and therefore skipping its event. This is intentional: for example, `MIGRATING_IN` channels are treated as fully operational home-chain channels, and similarly, channel states involved in escrow flows can be advanced by any valid newer state. + +External consumers — indexers, SDKs, analytics — **must not treat these events as exhaustive signals**. The canonical, always-guaranteed terminal events for each cross-chain flow (`MigrationOutFinalized`, `EscrowDepositInitiated`, `EscrowWithdrawalFinalized`, etc.) remain reliable. + +The following events are informational: + +| Event | When it may be skipped | +| --- | --- | +| `MigrationInFinalized` | A `MIGRATING_IN` channel transitions to `OPERATING` via any standard operation (deposit, withdraw, checkpoint, challenge) rather than explicit `finalizeMigration()` | +| `MigrationOutInitiated` | A newer `MIGRATING_OUT` signed state is enforced on the old home channel that is the successor of the explicit initiation state | +| `EscrowDepositFinalized` | The non-home channel advances past escrow finalization via a newer signed state | +| `EscrowDepositFinalizedOnHome` | The home channel advances past the escrow finalization acknowledgement via a newer signed state | +| `EscrowWithdrawalInitiatedOnHome` | The home channel advances past the escrow withdrawal initiation acknowledgement via a newer signed state | +| `EscrowWithdrawalFinalizedOnHome` | The home channel advances past the escrow withdrawal finalization acknowledgement via a newer signed state | + +The Nitronode does not rely on any of these informational events for its state machine. For migration, the Nitronode watches `MigrationInInitiated` (the on-chain request establishing the new home chain) and `MigrationOutFinalized` (the unconditional completion signal on the old home chain). Both are guaranteed events. + +--- diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1/run-1779450505626.json b/contracts/broadcast/DeployChannelHub.s.sol/1/run-1779450505626.json new file mode 100644 index 000000000..39fff6a10 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1/run-1779450505626.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x25567dbd47b3982a2dcf338637091193c8495b7ab1dd9d50a11f39340cb39ed4", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xeccf08e1435693e10907996a5271c68064579ef9e5ae183cf6392da3ab637bb2", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x0b0f8b27f8ec602b78774f802237a332a2520c950fe5e530cccf385a36c5a47a", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xca8853654985715283a8faa4a6b71d1c8de4eb078b8f2b910cf29918c66da30c", + "transactionType": "CREATE", + "contractName": "ECDSASignatureValidator", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x1637d08de07708ff81c29bc260edbcdfffae06b4c0dca1283c4ec1909e483574", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xaf77dc", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x25567dbd47b3982a2dcf338637091193c8495b7ab1dd9d50a11f39340cb39ed4", + "transactionIndex": "0x5b", + "blockHash": "0xccdee359ca75cb4def58c0e5b76de010357a0ff1e5c3340a18b86f11609d47e6", + "blockNumber": "0x17fc482", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x620ccd1", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xc16d75", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x0b0f8b27f8ec602b78774f802237a332a2520c950fe5e530cccf385a36c5a47a", + "transactionIndex": "0x68", + "blockHash": "0xccdee359ca75cb4def58c0e5b76de010357a0ff1e5c3340a18b86f11609d47e6", + "blockNumber": "0x17fc482", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x620ccd1", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x16ee872", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xeccf08e1435693e10907996a5271c68064579ef9e5ae183cf6392da3ab637bb2", + "transactionIndex": "0xe5", + "blockHash": "0x742550d66403424888bbef60cb43479fccbde9a21f9fe80191ed8088120c053b", + "blockNumber": "0x17fc483", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x6218f5f", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xeb5193", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xca8853654985715283a8faa4a6b71d1c8de4eb078b8f2b910cf29918c66da30c", + "transactionIndex": "0x8f", + "blockHash": "0xb263d856af51435d4163f19610c7cf381706198afb6e94f5d7127a2e732b032d", + "blockNumber": "0x17fc484", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x64019b8", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1d2f9eb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1637d08de07708ff81c29bc260edbcdfffae06b4c0dca1283c4ec1909e483574", + "transactionIndex": "0x9e", + "blockHash": "0xbfa060e6c5cab188c683b025f7e6395edf6e732b117cec04b8bbf1e8d2482927", + "blockNumber": "0x17fc486", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x5bdad09", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779450505626, + "chain": 1, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/1/run-latest.json new file mode 100644 index 000000000..152db7a55 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1/run-latest.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x25567dbd47b3982a2dcf338637091193c8495b7ab1dd9d50a11f39340cb39ed4", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xeccf08e1435693e10907996a5271c68064579ef9e5ae183cf6392da3ab637bb2", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x0b0f8b27f8ec602b78774f802237a332a2520c950fe5e530cccf385a36c5a47a", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xca8853654985715283a8faa4a6b71d1c8de4eb078b8f2b910cf29918c66da30c", + "transactionType": "CREATE", + "contractName": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x1637d08de07708ff81c29bc260edbcdfffae06b4c0dca1283c4ec1909e483574", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xaf77dc", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x25567dbd47b3982a2dcf338637091193c8495b7ab1dd9d50a11f39340cb39ed4", + "transactionIndex": "0x5b", + "blockHash": "0xccdee359ca75cb4def58c0e5b76de010357a0ff1e5c3340a18b86f11609d47e6", + "blockNumber": "0x17fc482", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x620ccd1", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xc16d75", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x0b0f8b27f8ec602b78774f802237a332a2520c950fe5e530cccf385a36c5a47a", + "transactionIndex": "0x68", + "blockHash": "0xccdee359ca75cb4def58c0e5b76de010357a0ff1e5c3340a18b86f11609d47e6", + "blockNumber": "0x17fc482", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x620ccd1", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x16ee872", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xeccf08e1435693e10907996a5271c68064579ef9e5ae183cf6392da3ab637bb2", + "transactionIndex": "0xe5", + "blockHash": "0x742550d66403424888bbef60cb43479fccbde9a21f9fe80191ed8088120c053b", + "blockNumber": "0x17fc483", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x6218f5f", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xeb5193", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xca8853654985715283a8faa4a6b71d1c8de4eb078b8f2b910cf29918c66da30c", + "transactionIndex": "0x8f", + "blockHash": "0xb263d856af51435d4163f19610c7cf381706198afb6e94f5d7127a2e732b032d", + "blockNumber": "0x17fc484", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x64019b8", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1d2f9eb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1637d08de07708ff81c29bc260edbcdfffae06b4c0dca1283c4ec1909e483574", + "transactionIndex": "0x9e", + "blockHash": "0xbfa060e6c5cab188c683b025f7e6395edf6e732b117cec04b8bbf1e8d2482927", + "blockNumber": "0x17fc486", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x5bdad09", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779450505626, + "chain": 1, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1776676404535.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1776676404535.json new file mode 100644 index 000000000..72408f4cf --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1776676404535.json @@ -0,0 +1,87 @@ +{ + "transactions": [ + { + "hash": "0x00dd759837ba610d871720c093e6ae25a01dae1aae2061a800dab828df2b1868", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x165fe1a7d70dcf7594442ca9e2a572aa43b79e07", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea264697066735822122095e45fd8b2cc47ea030c67fe68509a60a481b4484daf7ffd997c964f1bf115f464736f6c634300081e0033", + "nonce": "0x33", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x54f8602c28e2937a0d52420a605ce41e551e2773a45e1ac141a25941592f9a23", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x3497229ed24ad7877160923a39b982fed7a91e31", + "function": null, + "arguments": [ + "0x165fe1a7d70DcF7594442CA9E2A572aA43b79E07", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x675df2", + "value": "0x0", + "input": "0x60c03461010b57601f615de938819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615cc5908161012482396080518181816110c80152613e11015260a051818181610bab01528181610cbe01528181611387015281816119d601528181611fa40152818161354c01528181613fbe0152818161456f01526146510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461026f57806316b390b11461026a578063187576d8146102655780633115f6301461026057806338a66be21461025b5780633c684f921461025657806341b660ef1461025157806347de477a1461024c57806351bfcdbd1461024757806353269198146102425780635a0745b41461023d5780635ae2accc146102385780635b9acbf9146102335780635dc46a741461022e5780636840dbd2146102295780636898234b1461022457806371a471411461021f578063735181f01461021a57806382d3e15d146102155780638d0b12a5146102105780638e31c7351461020b57806394191051146102015780639691b46814610206578063a459463114610201578063a5c82680146101fc578063b25a1d38146101f7578063b65b78d1146101f2578063c74a2d10146101ed578063c9408398146101e8578063d888ccae146101e3578063d91a1283146101de578063dc23f29e146101d9578063dd73d494146101d4578063e617208c146101cf578063f4ac51f5146101ca578063f766f8d6146101c5578063ff5bc09e146101c05763ffa1ad74146101bb575f80fd5b6125dd565b6125c6565b6124a7565b61242c565b61238e565b61220c565b612055565b611f39565b611e30565b611ba7565b611b27565b611a38565b6116a7565b611548565b61141e565b61143b565b6112bb565b611174565b611157565b611111565b6110a9565b610fc4565b610fad565b610f62565b610f40565b610f25565b610f09565b610d11565b610c9f565b610af7565b6107fa565b610734565b6106f9565b61055d565b6104d7565b610341565b610289565b6001600160a01b0381160361028557565b5f80fd5b34610285576020366003190112610285576001600160a01b036004356102ae81610274565b165f526006602052602060405f2054604051908152f35b9181601f84011215610285578235916001600160401b038311610285576020838186019501011161028557565b60643590600282101561028557565b9060606003198301126102855760043591602435906001600160401b03821161028557610330916004016102c5565b909160443560028110156102855790565b34610285576103a36103dd61035536610301565b9294916103b8610370879693965f52600260205260405f2090565b9485549261037f8415156125f8565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613df0565b9192909901986103b28a612812565b87613f21565b60c06103c387614013565b604051809481926301999b9360e61b835260048301612982565b038173b5c2e8bad7417e654e9087753ec1d08762a06f915af48015610499577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610451946080945f93610466575b5082610443939461043c89612812565b908b614087565b01516001600160401b031690565b9061046160405192839283612abd565b0390a2005b610443935061048c9060c03d60c011610492575b610484818361268e565b8101906128c0565b9261042c565b503d61047a565b612993565b60206040818301928281528451809452019201905f5b8181106104c15750505090565b82518452602093840193909201916001016104b4565b34610285576020366003190112610285576001600160a01b036004356104fc81610274565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061054757610543856105378187038261268e565b6040519182918261049e565b0390f35b8254845260209093019260019283019201610520565b34610285576020366003190112610285576004355f905f60035491600454925b808410806106f0575b156106e5576105bb6105b56105a761059d87613074565b90549060031b1c90565b5f52600260205260405f2090565b936138d4565b946105c584615439565b6106d3576105d284615469565b15610690575f516020615c705f395f51905f526001600160a01b0361067961067361065e945f610610600c8b01546001600160a01b039060401c1690565b9961066d60016106318d6001600160a01b03165f52600660205260405f2090565b54928d610644600483019586549061324e565b9b8c916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556138d4565b976138d4565b604051938452951691602090a25b9391929361057d565b945050505061069e90600455565b806106a557005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b936106df9193506138d4565b91610687565b50505060045561069e565b50818310610586565b34610285575f366003190112610285576020604051620186a08152f35b6004359060ff8216820361028557565b359060ff8216820361028557565b346102855760203660031901126102855760ff61074f610716565b165f52600760205260405f2060405160408101918183106001600160401b038411176107ad576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b61260d565b90816102609103126102855790565b90600319820160e081126102855760c0136102855760049160c435906001600160401b038211610285576107f7916004016107b2565b90565b610803366107c1565b60208101600261081282612aee565b61081b81611c8f565b148015610adc575b8015610abe575b61083390612af8565b600261083e82612aee565b61084781611c8f565b03610aaf575b61090461086261085d3686612b3d565b61448f565b9261089061088161087a865f525f60205260405f2090565b5460ff1690565b61088a816122e2565b15612bb1565b61089c60208601612bc7565b906108a686614532565b6108b6608087013583838861460f565b60a0816108e96108e26108cb60808401612bc7565b6001600160a01b03165f52600660205260405f2090565b5488614676565b604051632a2d120f60e21b8152958692839260048401612def565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4908115610499577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e4946109796109fa936001600160a01b03965f91610a80575b50610968368b612b3d565b6109723686612ef3565b908a6147c2565b61099d87610998866001600160a01b03165f52600160205260405f2090565b61586d565b5060026109a982612aee565b6109b281611c8f565b036109ff5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f41778696206696604051806109e88582612f9f565b0390a25b604051938493169683612fb0565b0390a3005b610a0a600391612aee565b610a1381611c8f565b03610a5057857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a488582612f9f565b0390a26109ec565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a488582612f9f565b610aa2915060a03d60a011610aa8575b610a9a818361268e565b810190612bd1565b5f61095d565b503d610a90565b610ab93415612b0e565b61084d565b50610833610acb82612aee565b610ad481611c8f565b15905061082a565b506003610ae882612aee565b610af181611c8f565b14610823565b610b00366107c1565b90610b216004610b1260208501612aee565b610b1b81611c8f565b14612af8565b610b2a81614532565b610b3761085d3683612b3d565b916080610b4660208401612bc7565b92013591610b568382848761460f565b610b68610b628361303f565b856148d5565b92610b7285614904565b15610be85750506109fa7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610bd26001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613111565b610bdc8186614960565b60405191829182612f9f565b9091610c1460c082610bf987614013565b604051632ef10bcd60e21b8152938492839260048401613049565b038173b5c2e8bad7417e654e9087753ec1d08762a06f915af4928315610499577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca76946109fa94610c77935f91610c80575b50610c703686612ef3565b8989614087565b610bdc846130c3565b610c99915060c03d60c01161049257610484818361268e565b5f610c65565b34610285575f3660031901126102855760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102855760043591602435906001600160401b038211610285576107f7916004016107b2565b3461028557610d1f36610ce2565b610d306009610b1260208401612aee565b610d4c6001610d46845f525f60205260405f2090565b01613127565b610de7610d6360208301516001600160a01b031690565b91610d74608082015184868861460f565b610d7e3685612ef3565b61014085019386610d8e8661303f565b6001600160401b031646149586610ea1575b50505060a081610dcc610dc56108cb60206060850151016001600160a01b0390511690565b5489614676565b604051632a2d120f60e21b81529586928392600484016131b1565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af491821561049957610e19935f93610e80575b50866147c2565b15610e4f576104617f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182612f9f565b6104617f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182612f9f565b610e9a91935060a03d60a011610aa857610a9a818361268e565b915f610e12565b610f0092610eb3610efb923690612e14565b6060860152610ec53660608b01612e14565b6080860152610ed261319d565b60a0860152610edf61319d565b60c08601526001600160a01b03165f52600160205260405f2090565b615917565b505f8681610da0565b34610285575f366003190112610285576020604051612a308152f35b34610285575f36600319011261028557602060405160408152f35b346102855760403660031901126102855761054361053760243560043561327c565b3461028557610f79610f7336610ce2565b90613335565b005b6060600319820112610285576004359160243591604435906001600160401b038211610285576107f7916004016107b2565b3461028557610f79610fbe36610f7b565b91613685565b34610285576020366003190112610285576001600160a01b03600435610fe981610274565b165f526001602052610ffd60405f206157e1565b5f905f5b81518110156110965761102861087a61101a8385613268565b515f525f60205260405f2090565b611031816122e2565b60038114159081611081575b5061104b575b600101611001565b9161105e818460019310611066576138d4565b929050611043565b6110708585613268565b5161107b8286613268565b526138d4565b6005915061108e816122e2565b14155f61103d565b506105439181526040519182918261049e565b34610285575f3660031901126102855760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b60409060031901126102855760043561110481610274565b906024356107f781610274565b3461028557602061114e6001600160a01b0361112c366110ec565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610285575f366003190112610285576020600454604051908152f35b346102855761118236610301565b6111ce61119a859493945f52600560205260405f2090565b918254946111a98615156125f8565b60a06111b488614bcb565b604051809581926312031f5d60e11b8352600483016138e2565b038173af9f833141094083b9de2cb6aa0f7e1f2d2deee15af4908115610499577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103b296610451966060965f95611278575b50916112688596610443969385600561124c600161125c9901546001600160a01b039060081c1690565b97889360028401549a8b91613df0565b92909193019e8f612812565b61127189612812565b908b614c85565b610443955061125c939192966112a86112689260a03d60a0116112b4575b6112a0818361268e565b8101906135d4565b96509692919350611222565b503d611296565b34610285576060366003190112610285576112d4610716565b6024356112e081610274565b6044356001600160401b038111610285576113f3916113066113f89236906004016102c5565b9390946113b96113b460ff83169661131f8815156138f3565b6001600160a01b038616986113358a1515613909565b611376856113706113646113646113578460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b1561391f565b6113ae6113848b8730614d4d565b917f0000000000000000000000000000000000000000000000000000000000000000933691612ea2565b90614d85565b61393d565b6113d36113c46126af565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613953565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610285575f366003190112610285576020604051620151808152f35b34610285576114c461144c36610ce2565b61146d61145e60208395949501612aee565b61146781611c8f565b15612af8565b6114836001610d46855f525f60205260405f2090565b906114a861149b60208401516001600160a01b031690565b608084015190838761460f565b60a0816108e96114bd6108cb60808401612bc7565b5487614676565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361046193610bdc925f92611527575b506115203685612ef3565b90876147c2565b61154191925060a03d60a011610aa857610a9a818361268e565b905f611515565b3461028557611556366107c1565b906115686006610b1260208501612aee565b61157181614532565b61157e61085d3683612b3d565b91608061158d60208401612bc7565b9201359161159d8382848761460f565b6115a9610b628361303f565b926115b385614904565b156115e95750506109fa81610bdc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614960565b909161162560a08261160b6116046108cb6101608401612bc7565b5488614c28565b60405162ea54e760e01b815293849283926004840161366e565b038173af9f833141094083b9de2cb6aa0f7e1f2d2deee15af4928315610499577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f7946109fa94610bdc935f91611688575b506116813686612ef3565b8989614c85565b6116a1915060a03d60a0116112b4576112a0818361268e565b5f611676565b6080366003190112610285576004356024356001600160401b038111610285576116d59036906004016107b2565b6044356001600160401b038111610285576116f49036906004016102c5565b91906116fe6102f2565b92611710855f525f60205260405f2090565b91858461171f60018601613127565b9361172b865460ff1690565b93611735856122e2565b6001851494858015611a25575b61174b90612bb1565b61175760058901612812565b956117956117648661303f565b6001600160401b0361178c6117808b516001600160401b031690565b6001600160401b031690565b911610156139c1565b60208801516001600160a01b0316966001600160401b036117ce6117806117c060808d01519961303f565b93516001600160401b031690565b91161161188b575b5050946118317f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9989989661182b61185e9760149c61181f61184f996118469961187c9e613df0565b93919490923690612ef3565b90613f21565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b0342166139f7565b9301805467ffffffffffffffff19166001600160401b038516179055565b61046160405192839283613a17565b6119209495506004916118de916118c960208d9c9a9d9b969b01926118c460016118b486612aee565b6118bd81611c8f565b1415612af8565b6122e2565b80611a05575b6118d99015612af8565b612aee565b6118e781611c8f565b14806119d2575b6118f89015613111565b6119048489898d61460f565b60a0876108e96119196108cb60808401612bc7565b548d614676565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4918215610499577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9960149961182b8d8b61181f61185e9a6118319761187c9e61199d6118469c61184f9e5f916119b3575b506119963688612ef3565b8d896151af565b999e5099509950505097505096989950996117d6565b6119cc915060a03d60a011610aa857610a9a818361268e565b5f61198b565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118ee565b506118d96009611a1483612aee565b611a1d81611c8f565b1490506118cf565b50611a2f816122e2565b60048114611742565b604036600319011261028557600435611a5081610274565b6001600160a01b0360243591611a67831515613a37565b611a6f6154e4565b611a7a838233615378565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611b22575f516020615c705f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611b0f61046194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6131d6565b3461028557611b4c611b3836610ce2565b61146d6003610b1260208496959601612aee565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361046193610bdc925f9261152757506115203685612ef3565b34610285575f36600319011261028557600354600454905f805b82841015611c63577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611c0483615439565b611c5157611c1183615469565b15611c3a57611c31916004611c286105b5936138d4565b9401549061324e565b915b9192611bc1565b92509250505b604080519182526020820192909252f35b915092611c5d906138d4565b91611c33565b92509050611c40565b634e487b7160e01b5f52602160045260245ffd5b60041115611c8a57565b611c6c565b600a1115611c8a57565b90600a821015611c8a5752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6107f7916001600160401b038251168152611ced60208301516020830190611c99565b60408201516040820152611d5a6060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611ddb60a0840151610260610220850152610260840190611ca6565b92015190610240818403910152611ca6565b92936001600160401b0360c0956107f798979482948752611e0d81611c80565b602087015216604085015216606083015260808201528160a08201520190611cca565b3461028557602036600319011261028557600435611e4c613a83565b505f52600260205260405f2060405190611e6582612621565b80548252610543600182015491611eb0611ea0611e828560ff1690565b94611e91602088019687613ac7565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291611f286117c0611f06600560048501549460c0870195865201612812565b9360e0810194855251965197611f1b89611c80565b516001600160401b031690565b905191519260405196879687611ded565b3461028557606036600319011261028557600435611f5681610274565b5f516020615c705f395f51905f5261046160243592611f7484610274565b60443593611f8c6001600160a01b0383161515613909565b611f97851515613a37565b611fcb6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613ad3565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611b0f866120456001600160a01b038516988995865f5260066020526120228260405f205461201d82821015613ae9565b61325b565b978861203f836001600160a01b03165f52600660205260405f2090565b55615498565b6040519081529081906020820190565b3461028557612063366107c1565b6120746008610b1260208401612aee565b61208161085d3684612b3d565b916120e261209160208301612bc7565b916120a2608082013584868861460f565b6120ac3685612ef3565b6120b586614904565b938685156121ab575b505060a081610dcc610dc56108cb60206060850151016001600160a01b0390511690565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af49182156104995761211f935f93612186575b50612119903690612b3d565b866147c2565b15612155576104617f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182612f9f565b6104617f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182612f9f565b6121199193506121a49060a03d60a011610aa857610a9a818361268e565b929061210d565b6109986121c9926121bb86614532565b610eb3366101408b01612e14565b505f866120be565b9160a0936001600160401b03916107f797969385526121ef81611c80565b602085015216604083015260608201528160808201520190611cca565b3461028557602036600319011261028557600435612228613a83565b505f52600560205260405f20604051906122418261263d565b80548252610543600182015491612278611ea060ff851694602087019561226781611c80565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936122d16122bc600560048501549460a0850195865201612812565b9160c0810192835251945195611f1b87611c80565b9151905191604051958695866121d1565b60061115611c8a57565b906006821015611c8a5752565b91926123706101209461231385612383959a99989a6122ec565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b0360408201511660408501526001600160401b036060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611cca565b946101008201520152565b34610285576020366003190112610285576004355f60a06040516123b181612658565b82815282602082015282604082015282606082015282608082015201526123d6613a83565b505f525f6020526123e960405f20613b0b565b80516123f4816122e2565b610543602083015192604081015190606061241c61178060808401516001600160401b031690565b91015191604051958695866122f9565b61244c61243836610ce2565b61146d6002610b1260208496959601612aee565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361046193610bdc925f9261152757506115203685612ef3565b34610285576124b5366110ec565b6124bd6154e4565b6001600160a01b038116916124d3831515613909565b6001600160a01b03612510826124fa336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b549161251d831515613a37565b5f61253d826124fa336001600160a01b03165f52600860205260405f2090565b55169181836125b757612560915f808080858a5af161255a613b68565b50613b97565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610f7960017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6125c19184615542565b612560565b3461028557610f796125d736610f7b565b91613bbf565b34610285575f36600319011261028557602060405160018152f35b156125ff57565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b038211176107ad57604052565b60e081019081106001600160401b038211176107ad57604052565b60c081019081106001600160401b038211176107ad57604052565b60a081019081106001600160401b038211176107ad57604052565b90601f801991011681019081106001600160401b038211176107ad57604052565b604051906126be60408361268e565b565b604051906126be60e08361268e565b906040516126dc8161263d565b60c06004829461271960ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561276c575b602083101461275857565b634e487b7160e01b5f52602260045260245ffd5b91607f169161274d565b5f92918154916127858361273e565b80835292600181169081156127da57506001146127a157505050565b5f9081526020812093945091925b8383106127c0575060209250010190565b6001816020929493945483858701015201910191906127af565b915050602093945060ff929192191683830152151560051b010190565b906126be61280b9260405193848092612776565b038361268e565b9060405161281f8161263d565b809260ff81546001600160401b038116845260401c1690600a821015611c8a57600d6128909160c093602086015260018101546040860152612863600282016126cf565b6060860152612874600782016126cf565b6080860152612885600c82016127f7565b60a0860152016127f7565b910152565b5190600482101561028557565b6001600160401b0381160361028557565b5190811515820361028557565b908160c09103126102855761292860a0604051926128dd84612658565b80518452602081015160208501526128f760408201612895565b6040850152606081015161290a816128a2565b6060850152608081015161291d816128a2565b6080850152016128b3565b60a082015290565b90815161293c81611c80565b815260806001600160401b0381612962602086015160a0602087015260a0860190611cca565b946040810151604086015282606082015116606086015201511691015290565b9060206107f7928181520190612930565b6040513d5f823e3d90fd5b90600d6107f7926129c681546001600160401b038116855260ff602086019160401c16611c99565b60018101546040840152612a326060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612aad6102608401600c8301612776565b9261024081850391015201612776565b906001600160401b03612add60209295949560408552604085019061299e565b9416910152565b600a111561028557565b356107f781612ae4565b15612aff57565b633226144f60e21b5f5260045ffd5b15612b1557565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361028557565b35906126be826128a2565b91908260c091031261028557604051612b5581612658565b60a08082948035612b6581612b24565b84526020810135612b7581610274565b60208501526040810135612b8881610274565b60408501526060810135612b9b816128a2565b6060850152608081013560808501520135910152565b15612bb857565b631e40ad6360e31b5f5260045ffd5b356107f781610274565b908160a09103126102855760405190612be982612673565b80518252602081015160208301526040810151600681101561028557612c2a9160809160408501526060810151612c1f816128a2565b6060850152016128b3565b608082015290565b90612c3e8183516122ec565b60806001600160401b0381612c62602086015160a0602087015260a0860190611cca565b94604081015160408601526060810151606086015201511691015290565b35906126be82612ae4565b60c080916001600160401b038135612ca2816128a2565b1684526001600160a01b036020820135612cbb81610274565b16602085015260ff612ccf60408301610726565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102855701602081359101916001600160401b03821161028557813603831361028557565b908060209392818452848401375f828201840152601f01601f1916010190565b6107f7916001600160401b038235612d62816128a2565b168152612d806020830135612d7681612ae4565b6020830190611c99565b60408201356040820152612d9a6060820160608401612c8b565b612dac61014082016101408401612c8b565b612de0612dd4612dc0610220850185612cfa565b610260610220860152610260850191612d2b565b92610240810190612cfa565b91610240818503910152612d2b565b9091612e066107f793604084526040840190612c32565b916020818403910152612d4b565b91908260e091031261028557604051612e2c8161263d565b60c08082948035612e3c816128a2565b84526020810135612e4c81610274565b6020850152612e5d60408201610726565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b0381116107ad57601f01601f191660200190565b929192612eae82612e87565b91612ebc604051938461268e565b829481845281830111610285578281602093845f960137010152565b9080601f83011215610285578160206107f793359101612ea2565b9190916102608184031261028557612f096126c0565b92612f1382612b32565b8452612f2160208301612c80565b602085015260408201356040850152612f3d8160608401612e14565b6060850152612f50816101408401612e14565b60808501526102208201356001600160401b0381116102855781612f75918401612ed8565b60a08501526102408201356001600160401b03811161028557612f989201612ed8565b60c0830152565b9060206107f7928181520190612d4b565b60e09060a06107f7949363ffffffff8135612fca81612b24565b1683526001600160a01b036020820135612fe381610274565b1660208401526001600160a01b036040820135612fff81610274565b1660408401526001600160401b03606082013561301b816128a2565b16606084015260808101356080840152013560a08201528160c08201520190612d4b565b356107f7816128a2565b9091612e066107f793604084526040840190612930565b634e487b7160e01b5f52603260045260245ffd5b60035481101561308c5760035f5260205f2001905f90565b613060565b805482101561308c575f5260205f2001905f90565b916130bf9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156107ad576001810160035560035481101561308c5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b1561311857565b6370a8bfcd60e11b5f5260045ffd5b9060405161313481612658565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261318c6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b604051906131ac60208361268e565b5f8252565b90916131c86107f793604084526040840190612c32565b916020818403910152611cca565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116107ad5760051b60200190565b6040519061321060208361268e565b5f808352366020840137565b90613226826131ea565b613233604051918261268e565b8281528092613244601f19916131ea565b0190602036910137565b91908201809211611b2257565b91908203918211611b2257565b805182101561308c5760209160051b010190565b91906003549080840293808504821490151715611b22578184101561330057830190818411611b22578082116132f8575b506132c06132bb848361325b565b61321c565b92805b8281106132cf57505050565b806132de61059d600193613074565b6132f16132eb858461325b565b88613268565b52016132c3565b90505f6132ad565b505090506107f7613201565b906006811015611c8a5760ff80198354169116179055565b9060206107f7928181520190611cca565b90613347825f525f60205260405f2090565b61335360018201613127565b9161335f825460ff1690565b918461336d60058301612812565b91600261338460208801516001600160a01b031690565b9561338e816122e2565b148061357d575b6134a4575050506133ad6001610b1260208401612aee565b6133bd608084015183838761460f565b6133f060a0826133d56108e26108cb60808401612bc7565b604051632a2d120f60e21b8152938492839260048401612def565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4801561049957610efb61347e9461345a88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613471965f92613483575b506134533689612ef3565b90866147c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182612f9f565b0390a2565b61349d91925060a03d60a011610aa857610a9a818361268e565b905f613448565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061357092935061347e946135036014836134eb610efb95600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61345a6060860161352f8151606061352560208301516001600160a01b031690565b9101519085614a14565b5160a061354660208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614a14565b5060405191829182613324565b506014810154426001600160401b0390911610613395565b1561359c57565b6336c7a86b60e21b5f5260045ffd5b906135b581611c80565b60ff80198354169116179055565b9060206107f792818152019061299e565b908160a091031261028557612c2a6080604051926135f184612673565b805184526020810151602085015261360b60408201612895565b60408501526060810151612c1f816128a2565b90815161362a81611c80565b8152608080613648602085015160a0602086015260a0850190611cca565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612e066107f79360408452604084019061361e565b91613698825f52600560205260405f2090565b906136a38385614b7e565b61387c576136b384835414613595565b600182018054929060026136d6600886901c6001600160a01b03165b9560ff1690565b6136df81611c80565b1480613864575b61377d57506002906136ff6007610b1260208601612aee565b01549061370e8284838861460f565b61371d60a08261160b87614bcb565b038173af9f833141094083b9de2cb6aa0f7e1f2d2deee15af4928315610499577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461377894610bdc935f9161168857506116813686612ef3565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556137cf600383016001600160401b03198154169055565b5f516020615c705f395f51905f526001600160a01b03613822613800600c8601546001600160a01b039060401c1690565b9361381c856001600160a01b03165f52600660205260405f2090565b5461324e565b928361383f826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a2613854614389565b61377860405192839201826135c3565b506003820154426001600160401b03909116106136e6565b7f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d498915061377890610bdc6138cd60016138bc885f525f60205260405f2090565b015460201c6001600160a01b031690565b8287614ba0565b5f198114611b225760010190565b9060206107f792818152019061361e565b156138fa57565b6306ee4dcd60e01b5f5260045ffd5b1561391057565b63e6c4247b60e01b5f5260045ffd5b156139275750565b60ff906357470ffd60e01b5f521660045260245ffd5b1561394457565b63c1606c2f60e01b5f5260045ffd5b6001600160401b0360206126be936139986001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b156139c857565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611b2257565b906001600160401b03809116911601906001600160401b038211611b2257565b906001600160401b03612add602092959495604085526040850190612d4b565b15613a3e57565b6334b2073960e11b5f5260045ffd5b60405190613a5a8261263d565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613a908261263d565b606060c0835f81525f60208201525f6040820152613aac613a4d565b83820152613ab8613a4d565b60808201528260a08201520152565b613ad082611c80565b52565b15613ada57565b6308ad910960e21b5f5260045ffd5b15613af057565b631e9acf1760e31b5f5260045ffd5b6006821015611c8a5752565b90604051613b1881612673565b60806001600160401b0360148395613b3460ff82541686613aff565b613b4060018201613127565b6020860152613b5160058201612812565b604086015260138101546060860152015416910152565b3d15613b92573d90613b7982612e87565b91613b87604051938461268e565b82523d5f602084013e565b606090565b15613ba0575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613bd2825f52600260205260405f2090565b90613bdd838561559b565b613d5057613bed84835414613595565b60018201805492906002613c0d600886901c6001600160a01b03166136cf565b613c1681611c80565b1480613d2d575b613caf5750600290613c366005610b1260208601612aee565b015490613c458284838861460f565b613c5460c082610bf987614013565b038173b5c2e8bad7417e654e9087753ec1d08762a06f915af4928315610499577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461377894610bdc935f91610c805750610c703686612ef3565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613d259060048301905f82549255613d0e600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614a14565b613854614389565b50600382015460401c6001600160401b03166001600160401b0342911610613c1d565b7f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c915061377890610bdc6138cd60016138bc885f525f60205260405f2090565b15613d9757565b6306a41ced60e21b5f5260045ffd5b15613dae5750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613dcd575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613eca57801561308c57613e3f91843560f81c9081613e4357507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613e5684613e5d949060ff161c90565b1614613d90565b613ebd613e758260ff165f52600760205260405f2090565b546001600160a01b0381169290613eaa90613ea590613e9684871515613da6565b60a01c6001600160401b031690565b6139d7565b906001600160401b038216421015613dc4565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610285575190565b9392606093613f136001600160a01b0394612add949998998852608060208901526080880190611ca6565b918683036040880152612d2b565b9193929590613f2f906155b3565b916002821015611c8a576020956001600160a01b0392613fb857613f6b905b604051635850a09b60e11b81529889978896879560048701613ee8565b0392165afa8015610499576126be915f91613f89575b50151561393d565b613fab915060203d602011613fb1575b613fa3818361268e565b810190613ed9565b5f613f81565b503d613f99565b50613f6b7f0000000000000000000000000000000000000000000000000000000000000000613f4e565b60405190613fef82612673565b5f608083828152613ffe613a83565b60208201528260408201528260608201520152565b61401b613fe2565b905f5260026020526001600160401b0380600360405f2060ff60018201541661404381611c80565b855261405160058201612812565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611b22575f0390565b602093929161411b916140a2815f52600260205260405f2090565b976040860180516140b281611c80565b6140bb81611c80565b61436c575b50878560a08801946140d28651151590565b614359575b50505050506140f060608501516001600160401b031690565b6001600160401b038116614330575b5060808401516001600160401b0316806142f0575b5051151590565b156142d757608001518201516001600160a01b031680935b8251905f82131561429757614155915061414d84516157c5565b92839161536a565b6141646004860191825461324e565b90555b0180515f8113156141fc57505f516020615c705f395f51905f52916141946001600160a01b0392516157c5565b6141e560046141be836141b8866001600160a01b03165f52600660205260405f2090565b5461325b565b96876141db866001600160a01b03165f52600660205260405f2090565b550191825461324e565b90556040519384521691602090a25b6126be614389565b90505f811261420e575b5050506141f4565b5f516020615c705f395f51905f52916142366142316001600160a01b0393614077565b6157c5565b614281600461425a8361381c866001600160a01b03165f52600660205260405f2090565b9687614277866001600160a01b03165f52600660205260405f2090565b550191825461325b565b90556040519384521691602090a25f8080614206565b5f82126142a7575b505050614167565b6142b66142316142be93614077565b928391614a14565b6142cd6004860191825461325b565b9055825f8061429f565b50600c84015460401c6001600160a01b03168093614133565b61432a9060038901906fffffffffffffffff000000000000000082549160401b16906fffffffffffffffff00000000000000001916179055565b5f614114565b6143539060038901906001600160401b03166001600160401b0319825416179055565b5f6140ff565b614362946156cb565b5f808785826140d7565b614383905161437a81611c80565b60018b016135ab565b5f6140c0565b5f905f60035491600454925b80841080614485575b15614478576143b56105b56105a761059d87613074565b946143bf84615439565b614466576143cc84615469565b15614421575f516020615c705f395f51905f526001600160a01b0361440a61067361065e945f610610600c8b01546001600160a01b039060401c1690565b604051938452951691602090a25b93919293614395565b93919450506144309150600455565b806144385750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b936144729193506138d4565b91614418565b5092916144309150600455565b506040831061439e565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040519161451460208401809260a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b0360408201511660408501526001600160401b036060820151166060850152608081015160808501520151910152565b60c0835261452360e08461268e565b915190912016600160f81b1790565b6001600160a01b03602082013561454881610274565b16614554811515613909565b6001600160a01b03614599604084013561456d81610274565b7f0000000000000000000000000000000000000000000000000000000000000000831692168214613ad3565b81146145cb575063ffffffff6201518091356145b481612b24565b16106145bc57565b630596b15b60e01b5f5260045ffd5b63abfa558d60e01b5f5260045260245ffd5b903590601e198136030182121561028557018035906001600160401b0382116102855760200191813603831361028557565b90916126be9361463f61464d926146348361462e6102208901896145dd565b90613df0565b908888949394615829565b61462e6102408501856145dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615829565b9060146001600160401b039161468a613fe2565b935f525f60205260405f20906146a460ff83541686613aff565b6146b060058301612812565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556147b16001850161478461475b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926147fe8161484d946080946147df885f525f60205260405f2090565b976147eb895460ff1690565b6147f4816122e2565b156148c3576151af565b60408101805161480d816122e2565b614816816122e2565b151580614898575b61487e575b5060148401805460608301516001600160401b03908116911681900361485c575b50500151151590565b6148545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614844565b614892905161488c816122e2565b8561330c565b5f614823565b50845460ff168151906148aa826122e2565b6148b3826122e2565b6148bc816122e2565b141561481e565b6148d08260018b016146cf565b6151af565b906001600160401b036040519160208301938452166040820152604081526148fe60608261268e565b51902090565b805f525f60205260ff60405f2054166006811015611c8a57801590811561494c575b50614947575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614959816122e2565b145f614926565b906149b291805f525f60205261497b600160405f2001613127565b60a0836149976149906108cb60808401612bc7565b5485614676565b604051632a2d120f60e21b8152968792839260048401612def565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499576126be945f946149ef575b506149e9903690612ef3565b916147c2565b6149e9919450614a0d9060a03d60a011610aa857610a9a818361268e565b93906149dd565b90614a279291614a226154e4565b614a4d565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614b79576001600160a01b0383169283614af1576001600160a01b038216925f8080808488620186a0f1614a84613b68565b5015614a91575050505050565b614ad4613778926124fa7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614adf82825461324e565b90556040519081529081906020820190565b614b03614aff8484846159bd565b1590565b614b0e575b50505050565b81614b576001600160a01b03926124fa7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614b6285825461324e565b90556040519384521691602090a35f808080614b08565b505050565b905f52600560205260405f2054159081614b96575090565b6107f79150614904565b6149b29261497b614bbd6001610d46855f525f60205260405f2090565b91608083015190858561460f565b614bd3613fe2565b905f5260056020526001600160401b03600360405f2060ff600182015416614bfa81611c80565b8452614c0860058201612812565b60208501526004810154604085015201541660608201525f608082015290565b90614c31613fe2565b915f5260056020526001600160401b03600360405f2060ff600182015416614c5881611c80565b8552614c6660058201612812565b6020860152600481015460408601520154166060830152608082015290565b602093929161411b91614ca0815f52600560205260405f2090565b97604086018051614cb081611c80565b614cb981611c80565b614d39575b5087856080880194614cd08651151590565b614d26575b5050505050614cee60608501516001600160401b031690565b6001600160401b038116614d03575051151590565b61432a9060038901906001600160401b03166001600160401b0319825416179055565b614d2f94615a51565b5f80878582614cd5565b614d47905161437a81611c80565b5f614cbe565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526107f760a08261268e565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015614f21575b806d04ee2d6d415b85acef8100000000600a921015614f05575b662386f26fc10000811015614ef0575b6305f5e100811015614ede575b612710811015614ece575b6064811015614ebf575b1015614eb4575b614e4b6021614e1360018801615b0f565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614e5b57614e4b90614e18565b50506001600160a01b03614e8084614e74858498615aa3565b60208151910120615af9565b911693168314614eac57614e9e918160206113649351910120615af9565b14614ea7575f90565b600190565b505050600190565b600190940193614e02565b60029060649004960195614dfb565b6004906127109004960195614df1565b6008906305f5e1009004960195614de6565b601090662386f26fc100009004960195614dd9565b6020906d04ee2d6d415b85acef81000000009004960195614dc9565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614daf565b90600a811015611c8a5768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161502157505050565b5f5260205f20906020601f840160051c83019310615059575b601f0160051c01905b81811061504e575050565b5f8155600101615043565b909150819061503a565b91909182516001600160401b0381116107ad5761508a81615084845461273e565b84615014565b6020601f82116001146150c55781906130bf9394955f926150ba575b50508160011b915f199060031b1c19161790565b015190505f806150a6565b601f198216906150d8845f5260205f2090565b915f5b818110615112575095836001959697106150fa575b505050811b019055565b01515f1960f88460031b161c191690555f80806150f0565b9192602060018192868b0151815501940192016150db565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611c8a5760c0600d916151696126be9585614f49565b60408101516001850155615184606082015160028601614f76565b615195608082015160078601614f76565b6151a660a0820151600c8601615063565b01519101615063565b602060606151ea826151ce6151fb959896985f525f60205260405f2090565b976151dc8860058b0161512a565b01516001600160a01b031690565b94015101516001600160a01b031690565b809282515f811361533f575b50602083019283515f81136152be575b5051905f8212615296575b505050515f8112615239575b5050506126be614389565b5f516020615c705f395f51905f529161525c6142316001600160a01b0393614077565b615280601361425a8361381c866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061522e565b6142b66142316152a593614077565b6152b46013850191825461325b565b9055815f80615222565b6152c7906157c5565b6152e6816141b8866001600160a01b03165f52600660205260405f2090565b9081615303866001600160a01b03165f52600660205260405f2090565b556153136013890191825461324e565b90556040519081526001600160a01b038416905f516020615c705f395f51905f5290602090a25f615217565b615348906157c5565b61535381848461536a565b6153626013870191825461324e565b90555f615207565b90614a2792916153786154e4565b908215614b79576001600160a01b0316918215801561542a5761539c823414612b0e565b156153a657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561540b575b6040919091525f606052156153f05750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615421573d15833b151516166153de565b503d5f823e3d90fd5b6154343415612b0e565b61539c565b6001015460ff1661544981611c80565b60038114908115615458575090565b6002915061546581611c80565b1490565b6001600160401b036003820154164210159081615484575090565b600180925060ff9101541661546581611c80565b90614a2792916154a66154e4565b91908115614b79576001600160a01b031691826154db576126be92505f808080856001600160a01b0386165af161255a613b68565b6126be92615542565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146155335760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f5114811615615585575b604091909152156153f05750565b6001811516615421573d15833b15151616615577565b905f52600260205260405f2054159081614b96575090565b6001600160401b03815116906020810151600a811015611c8a5761565a8260406156ba9401516155fa60806060840151930151946040519760208901526040880190611c99565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526107f76102408261268e565b9190915f52600260205260405f20918255600582019261570b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611c8a5760c06157c19361572f60029761577794614f49565b6040810151600687015561574a606082015160078801614f76565b61575b6080820151600c8801614f76565b61576c60a082015160118801615063565b015160128501615063565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126157cf5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b8181106158105750506126be9250038361268e565b84548352600194850194879450602090930192016157fb565b6001600160a01b0390613f6b61584f61584a60209895999697993690612ef3565b6155b3565b936040519889978896879563600109bb60e01b875260048701613ee8565b6001810190825f528160205260405f2054155f146158d5578054680100000000000000008110156107ad576158c26158ac826001879401855584613091565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615903575f1901906158f28282613091565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f146159b5575f198401848111611b225783545f19810194908511611b22575f958583615972976159659503615978575b5050506158dc565b905f5260205260405f2090565b55600190565b61599e6159989161598f61059d6159ac9588613091565b92839187613091565b906130a6565b85905f5260205260405f2090565b555f808061595d565b505050505f90565b60405163a9059cbb60e01b602082019081526001600160a01b03939093166024820152604480820194909452928352915f9182916159fc60648261268e565b51908285620186a0f190615a0e613b68565b9115615a4b578151908115615a4257506020811015615a2d5750505f90565b81602091810103126102855760200151151590565b9150503b151590565b50505f90565b9190915f52600560205260405f20918255600582019261570b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b6126be90615aeb615ae594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615a91565b90615a91565b03601f19810184528361268e565b6107f791615b0691615b37565b90929192615b71565b90615b1982612e87565b615b26604051918261268e565b8281528092613244601f1991612e87565b8151919060418303615b6757615b609250602082015190606060408401519301515f1a90615bed565b9192909190565b50505f9160029190565b615b7a81611c80565b80615b83575050565b615b8c81611c80565b60018103615ba35763f645eedf60e01b5f5260045ffd5b615bac81611c80565b60028103615bc7575063fce698f760e01b5f5260045260245ffd5b80615bd3600392611c80565b14615bdb5750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615c64579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610499575f516001600160a01b03811615615c5a57905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea264697066735822122031a0d5c80492f37031544dc36300232be26e9efe9e84abb664e6e5215d179ed664736f6c634300081e0033000000000000000000000000165fe1a7d70dcf7594442ca9e2a572aa43b79e070000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0x34", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0xd2e90a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x00dd759837ba610d871720c093e6ae25a01dae1aae2061a800dab828df2b1868", + "transactionIndex": "0x3b", + "blockHash": "0x46ab1188a297d42d098c08ece1f51bad31220beb799d48105257f927565f266e", + "blockNumber": "0xa33607", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x11fa44db8", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x165fe1a7d70dcf7594442ca9e2a572aa43b79e07" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x13a2a82", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x54f8602c28e2937a0d52420a605ce41e551e2773a45e1ac141a25941592f9a23", + "transactionIndex": "0x49", + "blockHash": "0x46ab1188a297d42d098c08ece1f51bad31220beb799d48105257f927565f266e", + "blockNumber": "0xa33607", + "gasUsed": "0x4f8358", + "effectiveGasPrice": "0x11fa44db8", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x3497229ed24ad7877160923a39b982fed7a91e31" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x353A207A7bC822D8d3E58bcA2f3F9E2b90B26e78", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xb5c2e8BAD7417E654E9087753EC1D08762a06f91", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xaf9F833141094083B9de2cb6AA0f7E1f2d2DEee1" + ], + "pending": [], + "returns": {}, + "timestamp": 1776676404535, + "chain": 11155111, + "commit": "6c0a41d5" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779194593262.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779194593262.json new file mode 100644 index 000000000..ca706e4e5 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779194593262.json @@ -0,0 +1,189 @@ +{ + "transactions": [ + { + "hash": "0x992610754a5ff51aaf0f6f453d6fdfab05bdec11ed7b168075000f232c9ec945", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0xa023c476e02786356b84eda6ad6f8c003960f406", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea26469706673582212209929d28d6f4f84686b60107c75de0bf20707b30e60c2aea4ba079dccf78da9ab64736f6c634300081e0033", + "nonce": "0x35", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x125b9b598a99cf2bbf96ce56c4d72a5e6325498128f32faf7c8ba32c1ca26716", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0x899a6059e7824b7c5538dc7b3a2f5286013ed9d6", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e82", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea2646970667358221220854ea3b8ac791e9ce26e6783173cfd98de9d6f65d7e284de0500fdc6022468f264736f6c634300081e0033", + "nonce": "0x36", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x9c27b097aa025eb4192c58acb9754c9e68fe52fd5bb7ebd305fa6c48283cc286", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0x9f9513e2ea0ca353f2108552f8d6a2357c103d20", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea2646970667358221220e4948e1e731e3da271666ebc3a21498d041ba70ab4b8811a088372c3360fe38364736f6c634300081e0033", + "nonce": "0x37", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2ed88fa1af9f0513507fc508bdb9867d337657992a629fe93703318d56960a54", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x708b3ca8b7dc0f89ea5a06709c3b92dd5843b662", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea264697066735822122038c13cfc3dcd57a2af2b26923fa6b959f5116c33924710878a46f8a3dc1c3a3164736f6c634300081e0033", + "nonce": "0x38", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x15f0ed3d4aec486dc7a17ef3c4c77e54567f36ca0f6a8135aa7ab7d088e95bac", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xf74c93a176794337fb43c951cc0f6cef9a6723f6", + "function": null, + "arguments": [ + "0x708B3CA8b7Dc0f89Ea5a06709C3b92Dd5843B662", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x686f0b", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b0381739f9513e2ea0ca353f2108552f8d6a2357c103d205af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b0381739f9513e2ea0ca353f2108552f8d6a2357c103d205af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173899a6059e7824b7c5538dc7b3a2f5286013ed9d65af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173899a6059e7824b7c5538dc7b3a2f5286013ed9d65af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b038173a023c476e02786356b84eda6ad6f8c003960f4065af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173899a6059e7824b7c5538dc7b3a2f5286013ed9d65af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b0381739f9513e2ea0ca353f2108552f8d6a2357c103d205af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209734d5ca5df677a1bc2d6152129b8d22b047a6c8e88c7f12be97983785566ef764736f6c634300081e0033000000000000000000000000708b3ca8b7dc0f89ea5a06709c3b92dd5843b6620000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0x39", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x1a7a296", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x992610754a5ff51aaf0f6f453d6fdfab05bdec11ed7b168075000f232c9ec945", + "transactionIndex": "0xd0", + "blockHash": "0xde94f87032bac7bfe39b4d62ba936552b55474946ccd74a7200eec46a5c57d79", + "blockNumber": "0xa602e7", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0xcf9b34", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x1b2c31a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x125b9b598a99cf2bbf96ce56c4d72a5e6325498128f32faf7c8ba32c1ca26716", + "transactionIndex": "0xd1", + "blockHash": "0xde94f87032bac7bfe39b4d62ba936552b55474946ccd74a7200eec46a5c57d79", + "blockNumber": "0xa602e7", + "gasUsed": "0xb2084", + "effectiveGasPrice": "0xcf9b34", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x1bd8d14", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x9c27b097aa025eb4192c58acb9754c9e68fe52fd5bb7ebd305fa6c48283cc286", + "transactionIndex": "0xd2", + "blockHash": "0xde94f87032bac7bfe39b4d62ba936552b55474946ccd74a7200eec46a5c57d79", + "blockNumber": "0xa602e7", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0xcf9b34", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x1c3ef55", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x2ed88fa1af9f0513507fc508bdb9867d337657992a629fe93703318d56960a54", + "transactionIndex": "0xd3", + "blockHash": "0xde94f87032bac7bfe39b4d62ba936552b55474946ccd74a7200eec46a5c57d79", + "blockNumber": "0xa602e7", + "gasUsed": "0x66241", + "effectiveGasPrice": "0xcf9b34", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x708b3ca8b7dc0f89ea5a06709c3b92dd5843b662" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x21444c0", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x15f0ed3d4aec486dc7a17ef3c4c77e54567f36ca0f6a8135aa7ab7d088e95bac", + "transactionIndex": "0xd4", + "blockHash": "0xde94f87032bac7bfe39b4d62ba936552b55474946ccd74a7200eec46a5c57d79", + "blockNumber": "0xa602e7", + "gasUsed": "0x50556b", + "effectiveGasPrice": "0xcf9b34", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xf74c93a176794337fb43c951cc0f6cef9a6723f6" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0xa023C476E02786356b84EdA6ad6f8C003960F406", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x9F9513E2Ea0cA353f2108552F8d6A2357C103d20", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x899A6059E7824b7C5538DC7B3a2F5286013ED9D6" + ], + "pending": [], + "returns": {}, + "timestamp": 1779194593262, + "chain": 11155111, + "commit": "df4e110a" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779278401948.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779278401948.json new file mode 100644 index 000000000..11c70df96 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779278401948.json @@ -0,0 +1,156 @@ +{ + "transactions": [ + { + "hash": "0xce57f0cc9f8184819745729a47b8f4a399b1402557a979062a42fe4f7dab90d9", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x3b", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa3cd407e3e4288bf59982197df01a2ddfcf2bc91d399a8c5eb1438ee3c825091", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x3c", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xce9fcc6a0668dbe2025b191360ecb94da8e3d672ae3c1838f7ba3c85c355c3d5", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x3d", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xef53fd97c0bae85cfc0f6d5c918a65305429d85b8218c935f2e74c06e3647987", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xc9abf89607bb43ac41454c115bdcf58fe6a72d46", + "function": null, + "arguments": [ + "0xB5E7D2B8DB56A173Ca8c05CDdCC1379852CdC095", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "gas": "0x68906c", + "value": "0x0", + "input": "0x60c03461010b57601f615f0038819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615ddc908161012482396080518181816111960152613ef1015260a051818181610c6101528181610d7e0152818161145501528181611a5e0152818161209d0152818161363d0152818161409e01528181614667015261476f0152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461028457806316b390b11461027f578063187576d81461027a5780633115f6301461027557806338a66be2146102705780633c684f921461026b57806341b660ef1461026657806347de477a1461026157806351bfcdbd1461025c57806353269198146102575780635a0745b4146102525780635ae2accc1461024d5780635b9acbf9146102485780635dc46a74146102435780636840dbd21461023e5780636898234b1461023957806371a4714114610234578063735181f01461022f57806382d3e15d1461022a5780638d0b12a5146102255780638e31c73514610220578063941910511461021b5780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ce565b6126b7565b612598565b61251d565b61247f565b612305565b61214e565b612032565b611f29565b611c9a565b611c1a565b611bfd565b611b0e565b611790565b611631565b611616565b611509565b6114ec565b611389565b611242565b611225565b6111df565b611177565b611098565b611081565b611036565b611000565b610fe5565b610fc9565b610dd1565b610d5f565b610b9b565b610875565b6107b2565b610777565b610580565b6104fa565b610356565b61029e565b6001600160a01b0381160361029a57565b5f80fd5b3461029a57602036600319011261029a576001600160a01b036004356102c381610289565b165f526006602052602060405f2054604051908152f35b9181601f8401121561029a578235916001600160401b03831161029a576020838186019501011161029a57565b60643590600282101561029a57565b90606060031983011261029a5760043591602435906001600160401b03821161029a57610345916004016102da565b9091604435600281101561029a5790565b3461029a576103b86103f261036a36610316565b9294916103cd610385879693965f52600260205260405f2090565b948554926103948415156126e9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613ed0565b9192909901986103c78a612903565b87614001565b60c06103d8876140f3565b604051809481926301999b9360e61b835260048301612a73565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104ae577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610466946080945f9361047b575b5082610458939461045189612903565b908b614167565b01516001600160401b031690565b9061047660405192839283612bae565b0390a2005b61045893506104a19060c03d60c0116104a7575b610499818361277f565b8101906129b1565b92610441565b503d61048f565b612a84565b90602080835192838152019201905f5b8181106104d05750505090565b82518452602093840193909201916001016104c3565b9060206104f79281815201906104b3565b90565b3461029a57602036600319011261029a576001600160a01b0360043561051f81610289565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056a576105668561055a8187038261277f565b604051918291826104e6565b0390f35b8254845260209093019260019283019201610543565b3461029a57602036600319011261029a57600354600480545f929183903582841115610771576105b0838561334c565b8082101561076357506105c781959493929561330d565b925b8083108061075a575b1561074d576105ed6105e384613165565b90549060031b1c90565b610608610602825f52600260205260405f2090565b966139d6565b9561061281615577565b6107385761061f816155a7565b156106e8576001600160a01b036106d0610602600198999a6106b0955f866106bf610666600c5f516020615d875f395f51905f529a01546001600160a01b039060401c1690565b9d8e92610684846001600160a01b03165f52600660205260405f2090565b5493610696600483019586549061333f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106ca828d613359565b526139d6565b604051938452961691602090a25b94939291946105c9565b505050506106f891939250600455565b806106ff57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261073360405192839283614480565b0390a1005b505092939491610747906139d6565b926106de565b50506004559190506106f8565b508185106105d2565b6105c790959493929561330d565b5f6105b0565b3461029a575f36600319011261029a576020604051620186a08152f35b6004359060ff8216820361029a57565b359060ff8216820361029a57565b3461029a57602036600319011261029a5760ff6107cd610794565b165f52600760205260405f2060405160408101918183106001600160401b0384111761082b576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126fe565b908161026091031261029a5790565b90600319820160e0811261029a5760c01361029a5760049160c435906001600160401b03821161029a576104f791600401610830565b61087e3661083f565b60208101600261088d82612bdf565b61089681611d88565b148015610b80575b8015610b62575b6108ae90612be9565b60026108b982612bdf565b6108c281611d88565b03610b53575b6109a86109066108d83686612c2e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261093461092561091e865f525f60205260405f2090565b5460ff1690565b61092e816123db565b15612ca2565b61094060208601612cb8565b9061094a8661462c565b61095a608087013583838861472d565b60a08161098d61098661096f60808401612cb8565b6001600160a01b03165f52600660205260405f2090565b5488614794565b604051632a2d120f60e21b8152958692839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104ae577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a1d610a9e936001600160a01b03965f91610b24575b50610a0c368b612c2e565b610a163686612fe4565b908a6148e0565b610a4187610a3c866001600160a01b03165f52600160205260405f2090565b6159ab565b506002610a4d82612bdf565b610a5681611d88565b03610aa35750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a8c8582613090565b0390a25b6040519384931696836130a1565b0390a3005b610aae600391612bdf565b610ab781611d88565b03610af457857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610aec8582613090565b0390a2610a90565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610aec8582613090565b610b46915060a03d60a011610b4c575b610b3e818361277f565b810190612cc2565b5f610a01565b503d610b34565b610b5d3415612bff565b6108c8565b506108ae610b6f82612bdf565b610b7881611d88565b1590506108a5565b506003610b8c82612bdf565b610b9581611d88565b1461089e565b610ba43661083f565b90610bc56004610bb660208501612bdf565b610bbf81611d88565b14612be9565b610bce8161462c565b610bdb6108d83683612c2e565b916080610bea60208401612cb8565b92013591610bfa8382848761472d565b610c1e610c0683613130565b85906001600160401b03915f521660205260405f2090565b92610c28856149f3565b15610ca8575050610a9e7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c886001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613202565b610c923415612bff565b610c9c8186614a4f565b60405191829182613090565b9091610cd460c082610cb9876140f3565b604051632ef10bcd60e21b815293849283926004840161313a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104ae577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9e94610d37935f91610d40575b50610d303686612fe4565b8989614167565b610c9c846131b4565b610d59915060c03d60c0116104a757610499818361277f565b5f610d25565b3461029a575f36600319011261029a5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b90604060031983011261029a5760043591602435906001600160401b03821161029a576104f791600401610830565b3461029a57610ddf36610da2565b610df06009610bb660208401612bdf565b610e0c6001610e06845f525f60205260405f2090565b01613218565b610ea7610e2360208301516001600160a01b031690565b91610e34608082015184868861472d565b610e3e3685612fe4565b61014085019386610e4e86613130565b6001600160401b031646149586610f61575b50505060a081610e8c610e8561096f60206060850151016001600160a01b0390511690565b5489614794565b604051632a2d120f60e21b81529586928392600484016132a2565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae57610ed9935f93610f40575b50866148e0565b15610f0f576104767f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613090565b6104767f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613090565b610f5a91935060a03d60a011610b4c57610b3e818361277f565b915f610ed2565b610fc092610f73610fbb923690612f05565b6060860152610f853660608b01612f05565b6080860152610f9261328e565b60a0860152610f9f61328e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a55565b505f8681610e60565b3461029a575f36600319011261029a576020604051612a308152f35b3461029a575f36600319011261029a57602060405160408152f35b3461029a57604036600319011261029a5761056661102260243560043561336d565b6040519182916020835260208301906104b3565b3461029a5761104d61104736610da2565b90613426565b005b606060031982011261029a576004359160243591604435906001600160401b03821161029a576104f791600401610830565b3461029a5761104d6110923661104f565b91613776565b3461029a57602036600319011261029a576001600160a01b036004356110bd81610289565b165f5260016020526110d160405f2061591f565b5f905f5b8151811015611164576110fc61091e6110ee8385613359565b515f525f60205260405f2090565b611105816123db565b6003811415908161114f575b5061111f575b6001016110d5565b9161113281846001931061113a576139d6565b929050611117565b6111448585613359565b516106ca8286613359565b6005915061115c816123db565b14155f611111565b50610566918152604051918291826104e6565b3461029a575f36600319011261029a5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b604090600319011261029a576004356111d281610289565b906024356104f781610289565b3461029a57602061121c6001600160a01b036111fa366111ba565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b3461029a575f36600319011261029a576020600454604051908152f35b3461029a5761125036610316565b61129c611268859493945f52600560205260405f2090565b918254946112778615156126e9565b60a061128288614c8f565b604051809581926312031f5d60e11b8352600483016139e4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104ae577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c796610466966060965f95611346575b50916113368596610458969385600561131a600161132a9901546001600160a01b039060081c1690565b97889360028401549a8b91613ed0565b92909193019e8f612903565b61133f89612903565b908b614d49565b610458955061132a939192966113766113369260a03d60a011611382575b61136e818361277f565b8101906136c5565b965096929193506112f0565b503d611364565b3461029a57606036600319011261029a576113a2610794565b6024356113ae81610289565b6044356001600160401b03811161029a576114c1916113d46114c69236906004016102da565b93909461148761148260ff8316966113ed8815156139f5565b6001600160a01b038616986114038a1515613a0b565b6114448561143e6114326114326114258460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a21565b61147c6114528b8730614e80565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f93565b90614eb8565b613a3f565b6114a16114926127a0565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a55565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b3461029a575f36600319011261029a576020604051620151808152f35b3461029a5761159261151a36610da2565b61153b61152c60208395949501612bdf565b61153581611d88565b15612be9565b6115516001610e06855f525f60205260405f2090565b9061157661156960208401516001600160a01b031690565b608084015190838761472d565b60a08161098d61158b61096f60808401612cb8565b5487614794565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047693610c9c925f926115f5575b506115ee3685612fe4565b90876148e0565b61160f91925060a03d60a011610b4c57610b3e818361277f565b905f6115e3565b3461029a575f36600319011261029a576020604051603c8152f35b3461029a5761163f3661083f565b906116516006610bb660208501612bdf565b61165a8161462c565b6116676108d83683612c2e565b91608061167660208401612cb8565b920135916116868382848761472d565b611692610c0683613130565b9261169c856149f3565b156116d2575050610a9e81610c9c7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a4f565b909161170e60a0826116f46116ed61096f6101608401612cb8565b5488614cec565b60405162ea54e760e01b815293849283926004840161375f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104ae577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9e94610c9c935f91611771575b5061176a3686612fe4565b8989614d49565b61178a915060a03d60a0116113825761136e818361277f565b5f61175f565b608036600319011261029a576004356024356001600160401b03811161029a576117be903690600401610830565b6044356001600160401b03811161029a576117dd9036906004016102da565b90916117e7610307565b926117f9855f525f60205260405f2090565b61180560018201613218565b93611811825460ff1690565b9061181b826123db565b6001821495868015611afb575b61183190612ca2565b61183d60058501612903565b9261187b61184a88613130565b6001600160401b0361187261186688516001600160401b031690565b6001600160401b031690565b91161015613ac3565b60208201516001600160a01b0316978a6080840151956001600160401b036118b66118666118a88d613130565b93516001600160401b031690565b91161115611aad575061190b61194d9493926004926118f660208c01926118f160016118e186612bdf565b6118ea81611d88565b1415612be9565b6123db565b80611a8d575b6119069015612be9565b612bdf565b61191481611d88565b1480611a5a575b6119259015613202565b6119318489898d61472d565b60a08761098d61194661096f60808401612cb8565b548d614794565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119db8d8b6119cf611a0e9a6119e197611a2c9e6119ca6119f69c6119ff9e5f91611a3b575b506119c33688612fe4565b8d896152e2565b613ed0565b93919490923690612fe4565b90614001565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613af7565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047660405192839283613b17565b611a54915060a03d60a011610b4c57610b3e818361277f565b5f6119b8565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141561191b565b506119066009611a9c83612bdf565b611aa581611d88565b1490506118fc565b6119f69392506119e19150996014996119db7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119cf611a0e9a6119ff9a611a2c9e6119ca3415612bff565b50611b05836123db565b60048314611828565b604036600319011261029a57600435611b2681610289565b6001600160a01b0360243591611b3d831515613b37565b611b45615622565b611b508382336154b6565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bf8575f516020615d875f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611be561047694835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132c7565b3461029a575f36600319011261029a57602060405162093a808152f35b3461029a57611c3f611c2b36610da2565b61153b6003610bb660208496959601612bdf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047693610c9c925f926115f557506115ee3685612fe4565b3461029a575f36600319011261029a57600354600454905f805b82841015611d5c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cf783615577565b611d4a57611d04836155a7565b15611d3357611d2a916004611d1b611d24936139d6565b9401549061333f565b936139d6565b915b9192611cb4565b92509250505b604080519182526020820192909252f35b915092611d56906139d6565b91611d2c565b92509050611d39565b634e487b7160e01b5f52602160045260245ffd5b60041115611d8357565b611d65565b600a1115611d8357565b90600a821015611d835752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f7916001600160401b038251168152611de660208301516020830190611d92565b60408201516040820152611e536060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611ed460a0840151610260610220850152610260840190611d9f565b92015190610240818403910152611d9f565b92936001600160401b0360c0956104f798979482948752611f0681611d79565b602087015216604085015216606083015260808201528160a08201520190611dc3565b3461029a57602036600319011261029a57600435611f45613b83565b505f52600260205260405f2060405190611f5e82612712565b80548252610566600182015491611fa9611f99611f7b8560ff1690565b94611f8a602088019687613bc7565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a08201908152916120216118a8611fff600560048501549460c0870195865201612903565b9360e081019485525196519761201489611d79565b516001600160401b031690565b905191519260405196879687611ee6565b3461029a57606036600319011261029a5760043561204f81610289565b5f516020615d875f395f51905f526104766024359261206d84610289565b604435936120856001600160a01b0383161515613a0b565b612090851515613b37565b6120c46001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613202565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611be58661213e6001600160a01b038516988995865f52600660205261211b8260405f205461211682821015613bd3565b61334c565b9788612138836001600160a01b03165f52600660205260405f2090565b556155d6565b6040519081529081906020820190565b3461029a5761215c3661083f565b61216d6008610bb660208401612bdf565b61217a6108d83684612c2e565b916121db61218a60208301612cb8565b9161219b608082013584868861472d565b6121a53685612fe4565b6121ae866149f3565b938685156122a4575b505060a081610e8c610e8561096f60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae57612218935f9361227f575b50612212903690612c2e565b866148e0565b1561224e576104767f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613090565b6104767f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613090565b61221291935061229d9060a03d60a011610b4c57610b3e818361277f565b9290612206565b610a3c6122c2926122b48661462c565b610f73366101408b01612f05565b505f866121b7565b9160a0936001600160401b03916104f797969385526122e881611d79565b602085015216604083015260608201528160808201520190611dc3565b3461029a57602036600319011261029a57600435612321613b83565b505f52600560205260405f206040519061233a8261272e565b80548252610566600182015491612371611f9960ff851694602087019561236081611d79565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123ca6123b5600560048501549460a0850195865201612903565b9160c081019283525194519561201487611d79565b9151905191604051958695866122ca565b60061115611d8357565b906006821015611d835752565b919260a06101209461240b85612474959a99989a6123e5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611dc3565b946101008201520152565b3461029a57602036600319011261029a576004355f60a06040516124a281612749565b82815282602082015282604082015282606082015282608082015201526124c7613b83565b505f525f6020526124da60405f20613bf5565b80516124e5816123db565b610566602083015192604081015190606061250d61186660808401516001600160401b031690565b91015191604051958695866123f2565b61253d61252936610da2565b61153b6002610bb660208496959601612bdf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047693610c9c925f926115f557506115ee3685612fe4565b3461029a576125a6366111ba565b6125ae615622565b6001600160a01b038116916125c4831515613a0b565b6001600160a01b03612601826125eb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b549161260e831515613b37565b5f61262e826125eb336001600160a01b03165f52600860205260405f2090565b55169181836126a857612651915f808080858a5af161264b613c52565b50613c81565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104d60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126b29184615680565b612651565b3461029a5761104d6126c83661104f565b91613ca9565b3461029a575f36600319011261029a57602060405160018152f35b156126f057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082b57604052565b60e081019081106001600160401b0382111761082b57604052565b60c081019081106001600160401b0382111761082b57604052565b60a081019081106001600160401b0382111761082b57604052565b90601f801991011681019081106001600160401b0382111761082b57604052565b604051906127af60408361277f565b565b604051906127af60e08361277f565b906040516127cd8161272e565b60c06004829461280a60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561285d575b602083101461284957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161283e565b5f92918154916128768361282f565b80835292600181169081156128cb575060011461289257505050565b5f9081526020812093945091925b8383106128b1575060209250010190565b6001816020929493945483858701015201910191906128a0565b915050602093945060ff929192191683830152151560051b010190565b906127af6128fc9260405193848092612867565b038361277f565b906040516129108161272e565b809260ff81546001600160401b038116845260401c1690600a821015611d8357600d6129819160c093602086015260018101546040860152612954600282016127c0565b6060860152612965600782016127c0565b6080860152612976600c82016128e8565b60a0860152016128e8565b910152565b5190600482101561029a57565b6001600160401b0381160361029a57565b5190811515820361029a57565b908160c091031261029a57612a1960a0604051926129ce84612749565b80518452602081015160208501526129e860408201612986565b604085015260608101516129fb81612993565b60608501526080810151612a0e81612993565b6080850152016129a4565b60a082015290565b908151612a2d81611d79565b815260806001600160401b0381612a53602086015160a0602087015260a0860190611dc3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f7928181520190612a21565b6040513d5f823e3d90fd5b90600d6104f792612ab781546001600160401b038116855260ff602086019160401c16611d92565b60018101546040840152612b236060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b9e6102608401600c8301612867565b9261024081850391015201612867565b906001600160401b03612bce602092959495604085526040850190612a8f565b9416910152565b600a111561029a57565b356104f781612bd5565b15612bf057565b633226144f60e21b5f5260045ffd5b15612c0657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029a57565b35906127af82612993565b91908260c091031261029a57604051612c4681612749565b60a08082948035612c5681612c15565b84526020810135612c6681610289565b60208501526040810135612c7981610289565b60408501526060810135612c8c81612993565b6060850152608081013560808501520135910152565b15612ca957565b631e40ad6360e31b5f5260045ffd5b356104f781610289565b908160a091031261029a5760405190612cda82612764565b80518252602081015160208301526040810151600681101561029a57612d1b9160809160408501526060810151612d1081612993565b6060850152016129a4565b608082015290565b90612d2f8183516123e5565b60806001600160401b0381612d53602086015160a0602087015260a0860190611dc3565b94604081015160408601526060810151606086015201511691015290565b35906127af82612bd5565b60c080916001600160401b038135612d9381612993565b1684526001600160a01b036020820135612dac81610289565b16602085015260ff612dc0604083016107a4565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561029a5701602081359101916001600160401b03821161029a57813603831361029a57565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f7916001600160401b038235612e5381612993565b168152612e716020830135612e6781612bd5565b6020830190611d92565b60408201356040820152612e8b6060820160608401612d7c565b612e9d61014082016101408401612d7c565b612ed1612ec5612eb1610220850185612deb565b610260610220860152610260850191612e1c565b92610240810190612deb565b91610240818503910152612e1c565b9091612ef76104f793604084526040840190612d23565b916020818403910152612e3c565b91908260e091031261029a57604051612f1d8161272e565b60c08082948035612f2d81612993565b84526020810135612f3d81610289565b6020850152612f4e604082016107a4565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082b57601f01601f191660200190565b929192612f9f82612f78565b91612fad604051938461277f565b82948184528183011161029a578281602093845f960137010152565b9080601f8301121561029a578160206104f793359101612f93565b9190916102608184031261029a57612ffa6127b1565b9261300482612c23565b845261301260208301612d71565b60208501526040820135604085015261302e8160608401612f05565b6060850152613041816101408401612f05565b60808501526102208201356001600160401b03811161029a5781613066918401612fc9565b60a08501526102408201356001600160401b03811161029a576130899201612fc9565b60c0830152565b9060206104f7928181520190612e3c565b60e09060a06104f7949363ffffffff81356130bb81612c15565b1683526001600160a01b0360208201356130d481610289565b1660208401526001600160a01b0360408201356130f081610289565b1660408401526001600160401b03606082013561310c81612993565b16606084015260808101356080840152013560a08201528160c08201520190612e3c565b356104f781612993565b9091612ef76104f793604084526040840190612a21565b634e487b7160e01b5f52603260045260245ffd5b60035481101561317d5760035f5260205f2001905f90565b613151565b805482101561317d575f5260205f2001905f90565b916131b09183549060031b91821b915f19901b19161790565b9055565b6003546801000000000000000081101561082b576001810160035560035481101561317d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b1561320957565b6370a8bfcd60e11b5f5260045ffd5b9060405161322581612749565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261327d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061329d60208361277f565b5f8252565b90916132b96104f793604084526040840190612d23565b916020818403910152611dc3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b03811161082b5760051b60200190565b6040519061330160208361277f565b5f808352366020840137565b90613317826132db565b613324604051918261277f565b8281528092613335601f19916132db565b0190602036910137565b91908201809211611bf857565b91908203918211611bf857565b805182101561317d5760209160051b010190565b91906003549080840293808504821490151715611bf857818410156133f157830190818411611bf8578082116133e9575b506133b16133ac848361334c565b61330d565b92805b8281106133c057505050565b806133cf6105e3600193613165565b6133e26133dc858461334c565b88613359565b52016133b4565b90505f61339e565b505090506104f76132f2565b906006811015611d835760ff80198354169116179055565b9060206104f7928181520190611dc3565b90613438825f525f60205260405f2090565b61344460018201613218565b91613450825460ff1690565b918461345e60058301612903565b91600261347560208801516001600160a01b031690565b9561347f816123db565b148061366e575b6135955750505061349e6001610bb660208401612bdf565b6134ae608084015183838761472d565b6134e160a0826134c661098661096f60808401612cb8565b604051632a2d120f60e21b8152938492839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104ae57610fbb61356f9461354b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613562965f92613574575b506135443689612fe4565b90866148e0565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613090565b0390a2565b61358e91925060a03d60a011610b4c57610b3e818361277f565b905f613539565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061366192935061356f946135f46014836135dc610fbb95600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61354b606086016136208151606061361660208301516001600160a01b031690565b9101519085614b03565b5160a061363760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614b03565b5060405191829182613415565b506014810154426001600160401b0390911610613486565b1561368d57565b6336c7a86b60e21b5f5260045ffd5b906136a681611d79565b60ff80198354169116179055565b9060206104f7928181520190612a8f565b908160a091031261029a57612d1b6080604051926136e284612764565b80518452602081015160208501526136fc60408201612986565b60408501526060810151612d1081612993565b90815161371b81611d79565b8152608080613739602085015160a0602086015260a0850190611dc3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ef76104f79360408452604084019061370f565b916137818284614c6d565b61396d57613797825f52600560205260405f2090565b906137a484835414613686565b600182018054929060026137c7600886901c6001600160a01b03165b9560ff1690565b6137d081611d79565b1480613955575b61386e57506002906137f06007610bb660208601612bdf565b0154906137ff8284838861472d565b61380e60a0826116f487614c8f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104ae577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461386994610c9c935f91611771575061176a3686612fe4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138c0600383016001600160401b03198154169055565b5f516020615d875f395f51905f526001600160a01b036139136138f1600c8601546001600160a01b039060401c1690565b9361390d856001600160a01b03165f52600660205260405f2090565b5461333f565b9283613930826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261394561449c565b61386960405192839201826136b4565b506003820154426001600160401b03909116106137d7565b613869816139a36007610bb660207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bdf565b610c926139b7865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861472d565b5f198114611bf85760010190565b9060206104f792818152019061370f565b156139fc57565b6306ee4dcd60e01b5f5260045ffd5b15613a1257565b63e6c4247b60e01b5f5260045ffd5b15613a295750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a4657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b0360206127af93613a9a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aca57565b637d95736160e01b5f5260045ffd5b6001600160401b03603c911601906001600160401b038211611bf857565b906001600160401b03809116911601906001600160401b038211611bf857565b906001600160401b03612bce602092959495604085526040850190612e3c565b15613b3e57565b6334b2073960e11b5f5260045ffd5b60405190613b5a8261272e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b908261272e565b606060c0835f81525f60208201525f6040820152613bac613b4d565b83820152613bb8613b4d565b60808201528260a08201520152565b613bd082611d79565b52565b15613bda57565b631e9acf1760e31b5f5260045ffd5b6006821015611d835752565b90604051613c0281612764565b60806001600160401b0360148395613c1e60ff82541686613be9565b613c2a60018201613218565b6020860152613c3b60058201612903565b604086015260138101546060860152015416910152565b3d15613c7c573d90613c6382612f78565b91613c71604051938461277f565b82523d5f602084013e565b606090565b15613c8a575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613cb482846156d9565b613e3a57613cca825f52600260205260405f2090565b90613cd784835414613686565b60018201805492906002613cf7600886901c6001600160a01b03166137c0565b613d0081611d79565b1480613e17575b613d995750600290613d206005610bb660208601612bdf565b015490613d2f8284838861472d565b613d3e60c082610cb9876140f3565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104ae577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461386994610c9c935f91610d405750610d303686612fe4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613e0f9060048301905f82549255613df8600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614b03565b61394561449c565b50600382015460401c6001600160401b03166001600160401b0342911610613d07565b613869816139a36005610bb660207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bdf565b15613e7757565b6306a41ced60e21b5f5260045ffd5b15613e8e5750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613ead575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613faa57801561317d57613f1f91843560f81c9081613f2357507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f3684613f3d949060ff161c90565b1614613e70565b613f9d613f558260ff165f52600760205260405f2090565b546001600160a01b0381169290613f8a90613f8590613f7684871515613e86565b60a01c6001600160401b031690565b613ad9565b906001600160401b038216421015613ea4565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b9081602091031261029a575190565b9392606093613ff36001600160a01b0394612bce949998998852608060208901526080880190611d9f565b918683036040880152612e1c565b919392959061400f906156f1565b916002821015611d83576020956001600160a01b03926140985761404b905b604051635850a09b60e11b81529889978896879560048701613fc8565b0392165afa80156104ae576127af915f91614069575b501515613a3f565b61408b915060203d602011614091575b614083818361277f565b810190613fb9565b5f614061565b503d614079565b5061404b7f000000000000000000000000000000000000000000000000000000000000000061402e565b604051906140cf82612764565b5f6080838281526140de613b83565b60208201528260408201528260608201520152565b6140fb6140c2565b905f5260026020526001600160401b0380600360405f2060ff60018201541661412381611d79565b855261413160058201612903565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bf8575f0390565b936141d494602094939682614184835f52600260205260405f2090565b9860a08701956141948751151590565b156144675760808201518901516001600160a01b0316998a975b60408a018d81516141be81611d79565b6141c781611d79565b614449575b505051151590565b614436575b50505050506141f260608401516001600160401b031690565b6001600160401b03811661440d575b5060038601805460808501516001600160401b039081169160401c168190036143d6575b50505f8351135f14614389576142479061423f8451615903565b9283916154a8565b6142566004860191825461333f565b90555b0180515f8113156142ee57505f516020615d875f395f51905f52916142866001600160a01b039251615903565b6142d760046142b0836142aa866001600160a01b03165f52600660205260405f2090565b5461334c565b96876142cd866001600160a01b03165f52600660205260405f2090565b550191825461333f565b90556040519384521691602090a25b6127af61449c565b90505f8112614300575b5050506142e6565b5f516020615d875f395f51905f52916143286143236001600160a01b0393614157565b615903565b614373600461434c8361390d866001600160a01b03165f52600660205260405f2090565b9687614369866001600160a01b03165f52600660205260405f2090565b550191825461334c565b90556040519384521691602090a25f80806142f8565b6143933415612bff565b8251905f82126143a6575b505050614259565b6143b56143236143bd93614157565b928391614b03565b6143cc6004860191825461334c565b9055825f8061439e565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614225565b6144309060038801906001600160401b03166001600160401b0319825416179055565b5f614201565b61443f94615809565b5f808281806141d9565b600161446092519161445a83611d79565b0161369c565b5f8d6141cc565b600c8b015460401c6001600160a01b0316998a976141ae565b9291906144976020916040865260408601906104b3565b930152565b6003546004545f92839082841115614606576144b8838561334c565b806040105f146145f857506144d26040959493929561330d565b925b808310806145ee575b156145e0576144ee6105e384613165565b614503610602825f52600260205260405f2090565b9561450d81615577565b6145cb5761451a816155a7565b15614579576001600160a01b03614561610602600198999a6106b0955f866106bf610666600c5f516020615d875f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144d4565b5050509391925061458990600455565b80614592575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145c660405192839283614480565b0390a1565b5050929394916145da906139d6565b9261456f565b509391925061458990600455565b50604085106144dd565b6144d290959493929561330d565b5f6144b8565b356104f781612c15565b1561461d57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561464281610289565b1661464e811515613a0b565b6001600160a01b03604083013561466481610289565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ec5781146146da5750806201518063ffffffff6146ad6127af9461460c565b16101590816146bd575b50614616565b62093a8091506146d163ffffffff9161460c565b1611155f6146b7565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029a57018035906001600160401b03821161029a5760200191813603831361029a57565b90916127af9361475d61476b926147528361474c6102208901896146fb565b90613ed0565b908888949394615967565b61474c6102408501856146fb565b91937f000000000000000000000000000000000000000000000000000000000000000093615967565b9060146001600160401b03916147a86140c2565b935f525f60205260405f20906147c260ff83541686613be9565b6147ce60058301612903565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148cf600185016148a261487960408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b9261491c8161496b946080946148fd885f525f60205260405f2090565b97614909895460ff1690565b614912816123db565b156149e1576152e2565b60408101805161492b816123db565b614934816123db565b1515806149b6575b61499c575b5060148401805460608301516001600160401b03908116911681900361497a575b50500151151590565b6149725750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614962565b6149b090516149aa816123db565b856133fd565b5f614941565b50845460ff168151906149c8826123db565b6149d1826123db565b6149da816123db565b141561493c565b6149ee8260018b016147ed565b6152e2565b805f525f60205260ff60405f2054166006811015611d83578015908115614a3b575b50614a36575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a48816123db565b145f614a15565b90614aa191805f525f602052614a6a600160405f2001613218565b60a083614a86614a7f61096f60808401612cb8565b5485614794565b604051632a2d120f60e21b8152968792839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae576127af945f94614ade575b50614ad8903690612fe4565b916148e0565b614ad8919450614afc9060a03d60a011610b4c57610b3e818361277f565b9390614acc565b90614b169291614b11615622565b614b3c565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c68576001600160a01b0383169283614be0576001600160a01b038216925f8080808488620186a0f1614b73613c52565b5015614b80575050505050565b614bc3613869926125eb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bce82825461333f565b90556040519081529081906020820190565b614bf2614bee848484615afb565b1590565b614bfd575b50505050565b81614c466001600160a01b03926125eb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c5185825461333f565b90556040519384521691602090a35f808080614bf7565b505050565b905f52600560205260405f2054159081614c85575090565b6104f791506149f3565b614c976140c2565b905f5260056020526001600160401b03600360405f2060ff600182015416614cbe81611d79565b8452614ccc60058201612903565b60208501526004810154604085015201541660608201525f608082015290565b90614cf56140c2565b915f5260056020526001600160401b03600360405f2060ff600182015416614d1c81611d79565b8552614d2a60058201612903565b6020860152600481015460408601520154166060830152608082015290565b6020939291614dd491614d64815f52600560205260405f2090565b97604086018051614d7481611d79565b614d7d81611d79565b614e63575b5087856080880194614d948651151590565b614e50575b505050505060038701614db381546001600160401b031690565b60608601516001600160401b039081169116819003614e2e57505051151590565b15614e1557608001518201516001600160a01b031680935b8251905f821315614e0657614247915061423f8451615903565b5f82126143a657505050614259565b50600c84015460401c6001600160a01b03168093614dec565b815467ffffffffffffffff19166001600160401b039091161790555f806141cc565b614e5994615b68565b5f80878582614d99565b614e7a9051614e7181611d79565b60018b0161369c565b5f614d82565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f760a08261277f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615054575b806d04ee2d6d415b85acef8100000000600a921015615038575b662386f26fc10000811015615023575b6305f5e100811015615011575b612710811015615001575b6064811015614ff2575b1015614fe7575b614f7e6021614f4660018801615c26565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f8e57614f7e90614f4b565b50506001600160a01b03614fb384614fa7858498615bba565b60208151910120615c10565b911693168314614fdf57614fd1918160206114329351910120615c10565b14614fda575f90565b600190565b505050600190565b600190940193614f35565b60029060649004960195614f2e565b6004906127109004960195614f24565b6008906305f5e1009004960195614f19565b601090662386f26fc100009004960195614f0c565b6020906d04ee2d6d415b85acef81000000009004960195614efc565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ee2565b90600a811015611d835768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161515457505050565b5f5260205f20906020601f840160051c8301931061518c575b601f0160051c01905b818110615181575050565b5f8155600101615176565b909150819061516d565b91909182516001600160401b03811161082b576151bd816151b7845461282f565b84615147565b6020601f82116001146151f85781906131b09394955f926151ed575b50508160011b915f199060031b1c19161790565b015190505f806151d9565b601f1982169061520b845f5260205f2090565b915f5b8181106152455750958360019596971061522d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615223565b9192602060018192868b01518155019401920161520e565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d835760c0600d9161529c6127af958561507c565b604081015160018501556152b76060820151600286016150a9565b6152c86080820151600786016150a9565b6152d960a0820151600c8601615196565b01519101615196565b9161533160206152ff615323959694965f525f60205260405f2090565b9561531782606086015101516001600160a01b031690565b9586946005890161525d565b01516001600160a01b031690565b5f8351135f14615499576153458351615903565b6153508184846154a8565b61535f6013870191825461333f565b90555b602083019283515f8113615418575b5051905f82126153f0575b505050515f8112615393575b5050506127af61449c565b5f516020615d875f395f51905f52916153b66143236001600160a01b0393614157565b6153da601361434c8361390d866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f8080615388565b6143b56143236153ff93614157565b61540e6013850191825461334c565b9055815f8061537c565b61542190615903565b615440816142aa866001600160a01b03165f52600660205260405f2090565b908161545d866001600160a01b03165f52600660205260405f2090565b5561546d6013890191825461333f565b90556040519081526001600160a01b038416905f516020615d875f395f51905f5290602090a25f615371565b6154a33415612bff565b615362565b90614b1692916154b6615622565b908215614c68576001600160a01b03169182158015615568576154da823414612bff565b156154e457505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f5114811615615549575b6040919091525f6060521561552e5750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b600181151661555f573d15833b1515161661551c565b503d5f823e3d90fd5b6155723415612bff565b6154da565b6001015460ff1661558781611d79565b60038114908115615596575090565b600291506155a381611d79565b1490565b6001600160401b0360038201541642101590816155c2575090565b600180925060ff910154166155a381611d79565b90614b1692916155e4615622565b91908115614c68576001600160a01b03169182615619576127af92505f808080856001600160a01b0386165af161264b613c52565b6127af92615680565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156715760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156c3575b6040919091521561552e5750565b600181151661555f573d15833b151516166156b5565b905f52600260205260405f2054159081614c85575090565b6001600160401b03815116906020810151600a811015611d83576157988260406157f894015161573860806060840151930151946040519760208901526040880190611d92565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f76102408261277f565b9190915f52600260205260405f2091825560058201926158496001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d835760c06158ff9361586d6002976158b59461507c565b604081015160068701556158886060820151600788016150a9565b6158996080820151600c88016150a9565b6158aa60a082015160118801615196565b015160128501615196565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f811261590d5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061594e5750506127af9250038361277f565b8454835260019485019487945060209093019201615939565b6001600160a01b039061404b61598d61598860209895999697993690612fe4565b6156f1565b936040519889978896879563600109bb60e01b875260048701613fc8565b6001810190825f528160205260405f2054155f14615a135780546801000000000000000081101561082b57615a006159ea826001879401855584613182565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a41575f190190615a308282613182565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615af3575f198401848111611bf85783545f19810194908511611bf8575f958583615ab097615aa39503615ab6575b505050615a1a565b905f5260205260405f2090565b55600190565b615adc615ad691615acd6105e3615aea9588613182565b92839187613182565b90613197565b85905f5260205260405f2090565b555f8080615a9b565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b3960648261277f565b51908285620186a0f15f51913d9115615a13578115615b5f5750602011614a3657151590565b9150503b151590565b9190915f52600560205260405f2091825560058201926158496001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b6127af90615c02615bfc94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615ba8565b90615ba8565b03601f19810184528361277f565b6104f791615c1d91615c4e565b90929192615c88565b90615c3082612f78565b615c3d604051918261277f565b8281528092613335601f1991612f78565b8151919060418303615c7e57615c779250602082015190606060408401519301515f1a90615d04565b9192909190565b50505f9160029190565b615c9181611d79565b80615c9a575050565b615ca381611d79565b60018103615cba5763f645eedf60e01b5f5260045ffd5b615cc381611d79565b60028103615cde575063fce698f760e01b5f5260045260245ffd5b80615cea600392611d79565b14615cf25750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d7b579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104ae575f516001600160a01b03811615615d7157905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212205e68caa4b2d471ba6fc8e7a2cb4b8350ab1068b01b622da34e2d2daf0aa4dd6064736f6c634300081e0033000000000000000000000000b5e7d2b8db56a173ca8c05cddcc1379852cdc0950000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0x3e", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xf96249", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xce57f0cc9f8184819745729a47b8f4a399b1402557a979062a42fe4f7dab90d9", + "transactionIndex": "0x81", + "blockHash": "0xaf49b926e7aaafaf4d065fefa02b5ea7013838bab66eed14ce892a76498597fb", + "blockNumber": "0xa61989", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x3f25af6e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x10482d9", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa3cd407e3e4288bf59982197df01a2ddfcf2bc91d399a8c5eb1438ee3c825091", + "transactionIndex": "0x82", + "blockHash": "0xaf49b926e7aaafaf4d065fefa02b5ea7013838bab66eed14ce892a76498597fb", + "blockNumber": "0xa61989", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x3f25af6e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x10f4cd3", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xce9fcc6a0668dbe2025b191360ecb94da8e3d672ae3c1838f7ba3c85c355c3d5", + "transactionIndex": "0x83", + "blockHash": "0xaf49b926e7aaafaf4d065fefa02b5ea7013838bab66eed14ce892a76498597fb", + "blockNumber": "0xa61989", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x3f25af6e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x15fbbeb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xef53fd97c0bae85cfc0f6d5c918a65305429d85b8218c935f2e74c06e3647987", + "transactionIndex": "0x84", + "blockHash": "0xaf49b926e7aaafaf4d065fefa02b5ea7013838bab66eed14ce892a76498597fb", + "blockNumber": "0xa61989", + "gasUsed": "0x506f18", + "effectiveGasPrice": "0x3f25af6e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xc9abf89607bb43ac41454c115bdcf58fe6a72d46" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779278401948, + "chain": 11155111, + "commit": "9110ba06" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779286321195.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779286321195.json new file mode 100644 index 000000000..05567b951 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779286321195.json @@ -0,0 +1,54 @@ +{ + "transactions": [ + { + "hash": "0xd74da343a9f1079e3455d9d62396bb9b807a6b8e93e9727cf30c7f91adec5def", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x7d61ec428cfae560f43647af567ea7c6e2cc5527", + "function": null, + "arguments": [ + "0x708B3CA8b7Dc0f89Ea5a06709C3b92Dd5843B662", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "gas": "0x68906c", + "value": "0x0", + "input": "0x60c03461010b57601f615f0038819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615ddc908161012482396080518181816111960152613ef1015260a051818181610c6101528181610d7e0152818161145501528181611a5e0152818161209d0152818161363d0152818161409e01528181614667015261476f0152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461028457806316b390b11461027f578063187576d81461027a5780633115f6301461027557806338a66be2146102705780633c684f921461026b57806341b660ef1461026657806347de477a1461026157806351bfcdbd1461025c57806353269198146102575780635a0745b4146102525780635ae2accc1461024d5780635b9acbf9146102485780635dc46a74146102435780636840dbd21461023e5780636898234b1461023957806371a4714114610234578063735181f01461022f57806382d3e15d1461022a5780638d0b12a5146102255780638e31c73514610220578063941910511461021b5780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ce565b6126b7565b612598565b61251d565b61247f565b612305565b61214e565b612032565b611f29565b611c9a565b611c1a565b611bfd565b611b0e565b611790565b611631565b611616565b611509565b6114ec565b611389565b611242565b611225565b6111df565b611177565b611098565b611081565b611036565b611000565b610fe5565b610fc9565b610dd1565b610d5f565b610b9b565b610875565b6107b2565b610777565b610580565b6104fa565b610356565b61029e565b6001600160a01b0381160361029a57565b5f80fd5b3461029a57602036600319011261029a576001600160a01b036004356102c381610289565b165f526006602052602060405f2054604051908152f35b9181601f8401121561029a578235916001600160401b03831161029a576020838186019501011161029a57565b60643590600282101561029a57565b90606060031983011261029a5760043591602435906001600160401b03821161029a57610345916004016102da565b9091604435600281101561029a5790565b3461029a576103b86103f261036a36610316565b9294916103cd610385879693965f52600260205260405f2090565b948554926103948415156126e9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613ed0565b9192909901986103c78a612903565b87614001565b60c06103d8876140f3565b604051809481926301999b9360e61b835260048301612a73565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104ae577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610466946080945f9361047b575b5082610458939461045189612903565b908b614167565b01516001600160401b031690565b9061047660405192839283612bae565b0390a2005b61045893506104a19060c03d60c0116104a7575b610499818361277f565b8101906129b1565b92610441565b503d61048f565b612a84565b90602080835192838152019201905f5b8181106104d05750505090565b82518452602093840193909201916001016104c3565b9060206104f79281815201906104b3565b90565b3461029a57602036600319011261029a576001600160a01b0360043561051f81610289565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056a576105668561055a8187038261277f565b604051918291826104e6565b0390f35b8254845260209093019260019283019201610543565b3461029a57602036600319011261029a57600354600480545f929183903582841115610771576105b0838561334c565b8082101561076357506105c781959493929561330d565b925b8083108061075a575b1561074d576105ed6105e384613165565b90549060031b1c90565b610608610602825f52600260205260405f2090565b966139d6565b9561061281615577565b6107385761061f816155a7565b156106e8576001600160a01b036106d0610602600198999a6106b0955f866106bf610666600c5f516020615d875f395f51905f529a01546001600160a01b039060401c1690565b9d8e92610684846001600160a01b03165f52600660205260405f2090565b5493610696600483019586549061333f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106ca828d613359565b526139d6565b604051938452961691602090a25b94939291946105c9565b505050506106f891939250600455565b806106ff57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261073360405192839283614480565b0390a1005b505092939491610747906139d6565b926106de565b50506004559190506106f8565b508185106105d2565b6105c790959493929561330d565b5f6105b0565b3461029a575f36600319011261029a576020604051620186a08152f35b6004359060ff8216820361029a57565b359060ff8216820361029a57565b3461029a57602036600319011261029a5760ff6107cd610794565b165f52600760205260405f2060405160408101918183106001600160401b0384111761082b576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126fe565b908161026091031261029a5790565b90600319820160e0811261029a5760c01361029a5760049160c435906001600160401b03821161029a576104f791600401610830565b61087e3661083f565b60208101600261088d82612bdf565b61089681611d88565b148015610b80575b8015610b62575b6108ae90612be9565b60026108b982612bdf565b6108c281611d88565b03610b53575b6109a86109066108d83686612c2e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261093461092561091e865f525f60205260405f2090565b5460ff1690565b61092e816123db565b15612ca2565b61094060208601612cb8565b9061094a8661462c565b61095a608087013583838861472d565b60a08161098d61098661096f60808401612cb8565b6001600160a01b03165f52600660205260405f2090565b5488614794565b604051632a2d120f60e21b8152958692839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104ae577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a1d610a9e936001600160a01b03965f91610b24575b50610a0c368b612c2e565b610a163686612fe4565b908a6148e0565b610a4187610a3c866001600160a01b03165f52600160205260405f2090565b6159ab565b506002610a4d82612bdf565b610a5681611d88565b03610aa35750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a8c8582613090565b0390a25b6040519384931696836130a1565b0390a3005b610aae600391612bdf565b610ab781611d88565b03610af457857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610aec8582613090565b0390a2610a90565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610aec8582613090565b610b46915060a03d60a011610b4c575b610b3e818361277f565b810190612cc2565b5f610a01565b503d610b34565b610b5d3415612bff565b6108c8565b506108ae610b6f82612bdf565b610b7881611d88565b1590506108a5565b506003610b8c82612bdf565b610b9581611d88565b1461089e565b610ba43661083f565b90610bc56004610bb660208501612bdf565b610bbf81611d88565b14612be9565b610bce8161462c565b610bdb6108d83683612c2e565b916080610bea60208401612cb8565b92013591610bfa8382848761472d565b610c1e610c0683613130565b85906001600160401b03915f521660205260405f2090565b92610c28856149f3565b15610ca8575050610a9e7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c886001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613202565b610c923415612bff565b610c9c8186614a4f565b60405191829182613090565b9091610cd460c082610cb9876140f3565b604051632ef10bcd60e21b815293849283926004840161313a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104ae577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9e94610d37935f91610d40575b50610d303686612fe4565b8989614167565b610c9c846131b4565b610d59915060c03d60c0116104a757610499818361277f565b5f610d25565b3461029a575f36600319011261029a5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b90604060031983011261029a5760043591602435906001600160401b03821161029a576104f791600401610830565b3461029a57610ddf36610da2565b610df06009610bb660208401612bdf565b610e0c6001610e06845f525f60205260405f2090565b01613218565b610ea7610e2360208301516001600160a01b031690565b91610e34608082015184868861472d565b610e3e3685612fe4565b61014085019386610e4e86613130565b6001600160401b031646149586610f61575b50505060a081610e8c610e8561096f60206060850151016001600160a01b0390511690565b5489614794565b604051632a2d120f60e21b81529586928392600484016132a2565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae57610ed9935f93610f40575b50866148e0565b15610f0f576104767f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613090565b6104767f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613090565b610f5a91935060a03d60a011610b4c57610b3e818361277f565b915f610ed2565b610fc092610f73610fbb923690612f05565b6060860152610f853660608b01612f05565b6080860152610f9261328e565b60a0860152610f9f61328e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a55565b505f8681610e60565b3461029a575f36600319011261029a576020604051612a308152f35b3461029a575f36600319011261029a57602060405160408152f35b3461029a57604036600319011261029a5761056661102260243560043561336d565b6040519182916020835260208301906104b3565b3461029a5761104d61104736610da2565b90613426565b005b606060031982011261029a576004359160243591604435906001600160401b03821161029a576104f791600401610830565b3461029a5761104d6110923661104f565b91613776565b3461029a57602036600319011261029a576001600160a01b036004356110bd81610289565b165f5260016020526110d160405f2061591f565b5f905f5b8151811015611164576110fc61091e6110ee8385613359565b515f525f60205260405f2090565b611105816123db565b6003811415908161114f575b5061111f575b6001016110d5565b9161113281846001931061113a576139d6565b929050611117565b6111448585613359565b516106ca8286613359565b6005915061115c816123db565b14155f611111565b50610566918152604051918291826104e6565b3461029a575f36600319011261029a5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b604090600319011261029a576004356111d281610289565b906024356104f781610289565b3461029a57602061121c6001600160a01b036111fa366111ba565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b3461029a575f36600319011261029a576020600454604051908152f35b3461029a5761125036610316565b61129c611268859493945f52600560205260405f2090565b918254946112778615156126e9565b60a061128288614c8f565b604051809581926312031f5d60e11b8352600483016139e4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104ae577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c796610466966060965f95611346575b50916113368596610458969385600561131a600161132a9901546001600160a01b039060081c1690565b97889360028401549a8b91613ed0565b92909193019e8f612903565b61133f89612903565b908b614d49565b610458955061132a939192966113766113369260a03d60a011611382575b61136e818361277f565b8101906136c5565b965096929193506112f0565b503d611364565b3461029a57606036600319011261029a576113a2610794565b6024356113ae81610289565b6044356001600160401b03811161029a576114c1916113d46114c69236906004016102da565b93909461148761148260ff8316966113ed8815156139f5565b6001600160a01b038616986114038a1515613a0b565b6114448561143e6114326114326114258460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a21565b61147c6114528b8730614e80565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f93565b90614eb8565b613a3f565b6114a16114926127a0565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a55565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b3461029a575f36600319011261029a576020604051620151808152f35b3461029a5761159261151a36610da2565b61153b61152c60208395949501612bdf565b61153581611d88565b15612be9565b6115516001610e06855f525f60205260405f2090565b9061157661156960208401516001600160a01b031690565b608084015190838761472d565b60a08161098d61158b61096f60808401612cb8565b5487614794565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047693610c9c925f926115f5575b506115ee3685612fe4565b90876148e0565b61160f91925060a03d60a011610b4c57610b3e818361277f565b905f6115e3565b3461029a575f36600319011261029a576020604051603c8152f35b3461029a5761163f3661083f565b906116516006610bb660208501612bdf565b61165a8161462c565b6116676108d83683612c2e565b91608061167660208401612cb8565b920135916116868382848761472d565b611692610c0683613130565b9261169c856149f3565b156116d2575050610a9e81610c9c7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a4f565b909161170e60a0826116f46116ed61096f6101608401612cb8565b5488614cec565b60405162ea54e760e01b815293849283926004840161375f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104ae577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9e94610c9c935f91611771575b5061176a3686612fe4565b8989614d49565b61178a915060a03d60a0116113825761136e818361277f565b5f61175f565b608036600319011261029a576004356024356001600160401b03811161029a576117be903690600401610830565b6044356001600160401b03811161029a576117dd9036906004016102da565b90916117e7610307565b926117f9855f525f60205260405f2090565b61180560018201613218565b93611811825460ff1690565b9061181b826123db565b6001821495868015611afb575b61183190612ca2565b61183d60058501612903565b9261187b61184a88613130565b6001600160401b0361187261186688516001600160401b031690565b6001600160401b031690565b91161015613ac3565b60208201516001600160a01b0316978a6080840151956001600160401b036118b66118666118a88d613130565b93516001600160401b031690565b91161115611aad575061190b61194d9493926004926118f660208c01926118f160016118e186612bdf565b6118ea81611d88565b1415612be9565b6123db565b80611a8d575b6119069015612be9565b612bdf565b61191481611d88565b1480611a5a575b6119259015613202565b6119318489898d61472d565b60a08761098d61194661096f60808401612cb8565b548d614794565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119db8d8b6119cf611a0e9a6119e197611a2c9e6119ca6119f69c6119ff9e5f91611a3b575b506119c33688612fe4565b8d896152e2565b613ed0565b93919490923690612fe4565b90614001565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613af7565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047660405192839283613b17565b611a54915060a03d60a011610b4c57610b3e818361277f565b5f6119b8565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141561191b565b506119066009611a9c83612bdf565b611aa581611d88565b1490506118fc565b6119f69392506119e19150996014996119db7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119cf611a0e9a6119ff9a611a2c9e6119ca3415612bff565b50611b05836123db565b60048314611828565b604036600319011261029a57600435611b2681610289565b6001600160a01b0360243591611b3d831515613b37565b611b45615622565b611b508382336154b6565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bf8575f516020615d875f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611be561047694835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132c7565b3461029a575f36600319011261029a57602060405162093a808152f35b3461029a57611c3f611c2b36610da2565b61153b6003610bb660208496959601612bdf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047693610c9c925f926115f557506115ee3685612fe4565b3461029a575f36600319011261029a57600354600454905f805b82841015611d5c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cf783615577565b611d4a57611d04836155a7565b15611d3357611d2a916004611d1b611d24936139d6565b9401549061333f565b936139d6565b915b9192611cb4565b92509250505b604080519182526020820192909252f35b915092611d56906139d6565b91611d2c565b92509050611d39565b634e487b7160e01b5f52602160045260245ffd5b60041115611d8357565b611d65565b600a1115611d8357565b90600a821015611d835752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f7916001600160401b038251168152611de660208301516020830190611d92565b60408201516040820152611e536060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611ed460a0840151610260610220850152610260840190611d9f565b92015190610240818403910152611d9f565b92936001600160401b0360c0956104f798979482948752611f0681611d79565b602087015216604085015216606083015260808201528160a08201520190611dc3565b3461029a57602036600319011261029a57600435611f45613b83565b505f52600260205260405f2060405190611f5e82612712565b80548252610566600182015491611fa9611f99611f7b8560ff1690565b94611f8a602088019687613bc7565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a08201908152916120216118a8611fff600560048501549460c0870195865201612903565b9360e081019485525196519761201489611d79565b516001600160401b031690565b905191519260405196879687611ee6565b3461029a57606036600319011261029a5760043561204f81610289565b5f516020615d875f395f51905f526104766024359261206d84610289565b604435936120856001600160a01b0383161515613a0b565b612090851515613b37565b6120c46001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613202565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611be58661213e6001600160a01b038516988995865f52600660205261211b8260405f205461211682821015613bd3565b61334c565b9788612138836001600160a01b03165f52600660205260405f2090565b556155d6565b6040519081529081906020820190565b3461029a5761215c3661083f565b61216d6008610bb660208401612bdf565b61217a6108d83684612c2e565b916121db61218a60208301612cb8565b9161219b608082013584868861472d565b6121a53685612fe4565b6121ae866149f3565b938685156122a4575b505060a081610e8c610e8561096f60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae57612218935f9361227f575b50612212903690612c2e565b866148e0565b1561224e576104767f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613090565b6104767f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613090565b61221291935061229d9060a03d60a011610b4c57610b3e818361277f565b9290612206565b610a3c6122c2926122b48661462c565b610f73366101408b01612f05565b505f866121b7565b9160a0936001600160401b03916104f797969385526122e881611d79565b602085015216604083015260608201528160808201520190611dc3565b3461029a57602036600319011261029a57600435612321613b83565b505f52600560205260405f206040519061233a8261272e565b80548252610566600182015491612371611f9960ff851694602087019561236081611d79565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123ca6123b5600560048501549460a0850195865201612903565b9160c081019283525194519561201487611d79565b9151905191604051958695866122ca565b60061115611d8357565b906006821015611d835752565b919260a06101209461240b85612474959a99989a6123e5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611dc3565b946101008201520152565b3461029a57602036600319011261029a576004355f60a06040516124a281612749565b82815282602082015282604082015282606082015282608082015201526124c7613b83565b505f525f6020526124da60405f20613bf5565b80516124e5816123db565b610566602083015192604081015190606061250d61186660808401516001600160401b031690565b91015191604051958695866123f2565b61253d61252936610da2565b61153b6002610bb660208496959601612bdf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047693610c9c925f926115f557506115ee3685612fe4565b3461029a576125a6366111ba565b6125ae615622565b6001600160a01b038116916125c4831515613a0b565b6001600160a01b03612601826125eb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b549161260e831515613b37565b5f61262e826125eb336001600160a01b03165f52600860205260405f2090565b55169181836126a857612651915f808080858a5af161264b613c52565b50613c81565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104d60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126b29184615680565b612651565b3461029a5761104d6126c83661104f565b91613ca9565b3461029a575f36600319011261029a57602060405160018152f35b156126f057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082b57604052565b60e081019081106001600160401b0382111761082b57604052565b60c081019081106001600160401b0382111761082b57604052565b60a081019081106001600160401b0382111761082b57604052565b90601f801991011681019081106001600160401b0382111761082b57604052565b604051906127af60408361277f565b565b604051906127af60e08361277f565b906040516127cd8161272e565b60c06004829461280a60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561285d575b602083101461284957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161283e565b5f92918154916128768361282f565b80835292600181169081156128cb575060011461289257505050565b5f9081526020812093945091925b8383106128b1575060209250010190565b6001816020929493945483858701015201910191906128a0565b915050602093945060ff929192191683830152151560051b010190565b906127af6128fc9260405193848092612867565b038361277f565b906040516129108161272e565b809260ff81546001600160401b038116845260401c1690600a821015611d8357600d6129819160c093602086015260018101546040860152612954600282016127c0565b6060860152612965600782016127c0565b6080860152612976600c82016128e8565b60a0860152016128e8565b910152565b5190600482101561029a57565b6001600160401b0381160361029a57565b5190811515820361029a57565b908160c091031261029a57612a1960a0604051926129ce84612749565b80518452602081015160208501526129e860408201612986565b604085015260608101516129fb81612993565b60608501526080810151612a0e81612993565b6080850152016129a4565b60a082015290565b908151612a2d81611d79565b815260806001600160401b0381612a53602086015160a0602087015260a0860190611dc3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f7928181520190612a21565b6040513d5f823e3d90fd5b90600d6104f792612ab781546001600160401b038116855260ff602086019160401c16611d92565b60018101546040840152612b236060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b9e6102608401600c8301612867565b9261024081850391015201612867565b906001600160401b03612bce602092959495604085526040850190612a8f565b9416910152565b600a111561029a57565b356104f781612bd5565b15612bf057565b633226144f60e21b5f5260045ffd5b15612c0657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029a57565b35906127af82612993565b91908260c091031261029a57604051612c4681612749565b60a08082948035612c5681612c15565b84526020810135612c6681610289565b60208501526040810135612c7981610289565b60408501526060810135612c8c81612993565b6060850152608081013560808501520135910152565b15612ca957565b631e40ad6360e31b5f5260045ffd5b356104f781610289565b908160a091031261029a5760405190612cda82612764565b80518252602081015160208301526040810151600681101561029a57612d1b9160809160408501526060810151612d1081612993565b6060850152016129a4565b608082015290565b90612d2f8183516123e5565b60806001600160401b0381612d53602086015160a0602087015260a0860190611dc3565b94604081015160408601526060810151606086015201511691015290565b35906127af82612bd5565b60c080916001600160401b038135612d9381612993565b1684526001600160a01b036020820135612dac81610289565b16602085015260ff612dc0604083016107a4565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561029a5701602081359101916001600160401b03821161029a57813603831361029a57565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f7916001600160401b038235612e5381612993565b168152612e716020830135612e6781612bd5565b6020830190611d92565b60408201356040820152612e8b6060820160608401612d7c565b612e9d61014082016101408401612d7c565b612ed1612ec5612eb1610220850185612deb565b610260610220860152610260850191612e1c565b92610240810190612deb565b91610240818503910152612e1c565b9091612ef76104f793604084526040840190612d23565b916020818403910152612e3c565b91908260e091031261029a57604051612f1d8161272e565b60c08082948035612f2d81612993565b84526020810135612f3d81610289565b6020850152612f4e604082016107a4565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082b57601f01601f191660200190565b929192612f9f82612f78565b91612fad604051938461277f565b82948184528183011161029a578281602093845f960137010152565b9080601f8301121561029a578160206104f793359101612f93565b9190916102608184031261029a57612ffa6127b1565b9261300482612c23565b845261301260208301612d71565b60208501526040820135604085015261302e8160608401612f05565b6060850152613041816101408401612f05565b60808501526102208201356001600160401b03811161029a5781613066918401612fc9565b60a08501526102408201356001600160401b03811161029a576130899201612fc9565b60c0830152565b9060206104f7928181520190612e3c565b60e09060a06104f7949363ffffffff81356130bb81612c15565b1683526001600160a01b0360208201356130d481610289565b1660208401526001600160a01b0360408201356130f081610289565b1660408401526001600160401b03606082013561310c81612993565b16606084015260808101356080840152013560a08201528160c08201520190612e3c565b356104f781612993565b9091612ef76104f793604084526040840190612a21565b634e487b7160e01b5f52603260045260245ffd5b60035481101561317d5760035f5260205f2001905f90565b613151565b805482101561317d575f5260205f2001905f90565b916131b09183549060031b91821b915f19901b19161790565b9055565b6003546801000000000000000081101561082b576001810160035560035481101561317d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b1561320957565b6370a8bfcd60e11b5f5260045ffd5b9060405161322581612749565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261327d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061329d60208361277f565b5f8252565b90916132b96104f793604084526040840190612d23565b916020818403910152611dc3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b03811161082b5760051b60200190565b6040519061330160208361277f565b5f808352366020840137565b90613317826132db565b613324604051918261277f565b8281528092613335601f19916132db565b0190602036910137565b91908201809211611bf857565b91908203918211611bf857565b805182101561317d5760209160051b010190565b91906003549080840293808504821490151715611bf857818410156133f157830190818411611bf8578082116133e9575b506133b16133ac848361334c565b61330d565b92805b8281106133c057505050565b806133cf6105e3600193613165565b6133e26133dc858461334c565b88613359565b52016133b4565b90505f61339e565b505090506104f76132f2565b906006811015611d835760ff80198354169116179055565b9060206104f7928181520190611dc3565b90613438825f525f60205260405f2090565b61344460018201613218565b91613450825460ff1690565b918461345e60058301612903565b91600261347560208801516001600160a01b031690565b9561347f816123db565b148061366e575b6135955750505061349e6001610bb660208401612bdf565b6134ae608084015183838761472d565b6134e160a0826134c661098661096f60808401612cb8565b604051632a2d120f60e21b8152938492839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104ae57610fbb61356f9461354b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613562965f92613574575b506135443689612fe4565b90866148e0565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613090565b0390a2565b61358e91925060a03d60a011610b4c57610b3e818361277f565b905f613539565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061366192935061356f946135f46014836135dc610fbb95600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61354b606086016136208151606061361660208301516001600160a01b031690565b9101519085614b03565b5160a061363760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614b03565b5060405191829182613415565b506014810154426001600160401b0390911610613486565b1561368d57565b6336c7a86b60e21b5f5260045ffd5b906136a681611d79565b60ff80198354169116179055565b9060206104f7928181520190612a8f565b908160a091031261029a57612d1b6080604051926136e284612764565b80518452602081015160208501526136fc60408201612986565b60408501526060810151612d1081612993565b90815161371b81611d79565b8152608080613739602085015160a0602086015260a0850190611dc3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ef76104f79360408452604084019061370f565b916137818284614c6d565b61396d57613797825f52600560205260405f2090565b906137a484835414613686565b600182018054929060026137c7600886901c6001600160a01b03165b9560ff1690565b6137d081611d79565b1480613955575b61386e57506002906137f06007610bb660208601612bdf565b0154906137ff8284838861472d565b61380e60a0826116f487614c8f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104ae577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461386994610c9c935f91611771575061176a3686612fe4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138c0600383016001600160401b03198154169055565b5f516020615d875f395f51905f526001600160a01b036139136138f1600c8601546001600160a01b039060401c1690565b9361390d856001600160a01b03165f52600660205260405f2090565b5461333f565b9283613930826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261394561449c565b61386960405192839201826136b4565b506003820154426001600160401b03909116106137d7565b613869816139a36007610bb660207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bdf565b610c926139b7865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861472d565b5f198114611bf85760010190565b9060206104f792818152019061370f565b156139fc57565b6306ee4dcd60e01b5f5260045ffd5b15613a1257565b63e6c4247b60e01b5f5260045ffd5b15613a295750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a4657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b0360206127af93613a9a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aca57565b637d95736160e01b5f5260045ffd5b6001600160401b03603c911601906001600160401b038211611bf857565b906001600160401b03809116911601906001600160401b038211611bf857565b906001600160401b03612bce602092959495604085526040850190612e3c565b15613b3e57565b6334b2073960e11b5f5260045ffd5b60405190613b5a8261272e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b908261272e565b606060c0835f81525f60208201525f6040820152613bac613b4d565b83820152613bb8613b4d565b60808201528260a08201520152565b613bd082611d79565b52565b15613bda57565b631e9acf1760e31b5f5260045ffd5b6006821015611d835752565b90604051613c0281612764565b60806001600160401b0360148395613c1e60ff82541686613be9565b613c2a60018201613218565b6020860152613c3b60058201612903565b604086015260138101546060860152015416910152565b3d15613c7c573d90613c6382612f78565b91613c71604051938461277f565b82523d5f602084013e565b606090565b15613c8a575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613cb482846156d9565b613e3a57613cca825f52600260205260405f2090565b90613cd784835414613686565b60018201805492906002613cf7600886901c6001600160a01b03166137c0565b613d0081611d79565b1480613e17575b613d995750600290613d206005610bb660208601612bdf565b015490613d2f8284838861472d565b613d3e60c082610cb9876140f3565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104ae577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461386994610c9c935f91610d405750610d303686612fe4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613e0f9060048301905f82549255613df8600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614b03565b61394561449c565b50600382015460401c6001600160401b03166001600160401b0342911610613d07565b613869816139a36005610bb660207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bdf565b15613e7757565b6306a41ced60e21b5f5260045ffd5b15613e8e5750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613ead575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613faa57801561317d57613f1f91843560f81c9081613f2357507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f3684613f3d949060ff161c90565b1614613e70565b613f9d613f558260ff165f52600760205260405f2090565b546001600160a01b0381169290613f8a90613f8590613f7684871515613e86565b60a01c6001600160401b031690565b613ad9565b906001600160401b038216421015613ea4565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b9081602091031261029a575190565b9392606093613ff36001600160a01b0394612bce949998998852608060208901526080880190611d9f565b918683036040880152612e1c565b919392959061400f906156f1565b916002821015611d83576020956001600160a01b03926140985761404b905b604051635850a09b60e11b81529889978896879560048701613fc8565b0392165afa80156104ae576127af915f91614069575b501515613a3f565b61408b915060203d602011614091575b614083818361277f565b810190613fb9565b5f614061565b503d614079565b5061404b7f000000000000000000000000000000000000000000000000000000000000000061402e565b604051906140cf82612764565b5f6080838281526140de613b83565b60208201528260408201528260608201520152565b6140fb6140c2565b905f5260026020526001600160401b0380600360405f2060ff60018201541661412381611d79565b855261413160058201612903565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bf8575f0390565b936141d494602094939682614184835f52600260205260405f2090565b9860a08701956141948751151590565b156144675760808201518901516001600160a01b0316998a975b60408a018d81516141be81611d79565b6141c781611d79565b614449575b505051151590565b614436575b50505050506141f260608401516001600160401b031690565b6001600160401b03811661440d575b5060038601805460808501516001600160401b039081169160401c168190036143d6575b50505f8351135f14614389576142479061423f8451615903565b9283916154a8565b6142566004860191825461333f565b90555b0180515f8113156142ee57505f516020615d875f395f51905f52916142866001600160a01b039251615903565b6142d760046142b0836142aa866001600160a01b03165f52600660205260405f2090565b5461334c565b96876142cd866001600160a01b03165f52600660205260405f2090565b550191825461333f565b90556040519384521691602090a25b6127af61449c565b90505f8112614300575b5050506142e6565b5f516020615d875f395f51905f52916143286143236001600160a01b0393614157565b615903565b614373600461434c8361390d866001600160a01b03165f52600660205260405f2090565b9687614369866001600160a01b03165f52600660205260405f2090565b550191825461334c565b90556040519384521691602090a25f80806142f8565b6143933415612bff565b8251905f82126143a6575b505050614259565b6143b56143236143bd93614157565b928391614b03565b6143cc6004860191825461334c565b9055825f8061439e565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614225565b6144309060038801906001600160401b03166001600160401b0319825416179055565b5f614201565b61443f94615809565b5f808281806141d9565b600161446092519161445a83611d79565b0161369c565b5f8d6141cc565b600c8b015460401c6001600160a01b0316998a976141ae565b9291906144976020916040865260408601906104b3565b930152565b6003546004545f92839082841115614606576144b8838561334c565b806040105f146145f857506144d26040959493929561330d565b925b808310806145ee575b156145e0576144ee6105e384613165565b614503610602825f52600260205260405f2090565b9561450d81615577565b6145cb5761451a816155a7565b15614579576001600160a01b03614561610602600198999a6106b0955f866106bf610666600c5f516020615d875f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144d4565b5050509391925061458990600455565b80614592575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145c660405192839283614480565b0390a1565b5050929394916145da906139d6565b9261456f565b509391925061458990600455565b50604085106144dd565b6144d290959493929561330d565b5f6144b8565b356104f781612c15565b1561461d57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561464281610289565b1661464e811515613a0b565b6001600160a01b03604083013561466481610289565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ec5781146146da5750806201518063ffffffff6146ad6127af9461460c565b16101590816146bd575b50614616565b62093a8091506146d163ffffffff9161460c565b1611155f6146b7565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029a57018035906001600160401b03821161029a5760200191813603831361029a57565b90916127af9361475d61476b926147528361474c6102208901896146fb565b90613ed0565b908888949394615967565b61474c6102408501856146fb565b91937f000000000000000000000000000000000000000000000000000000000000000093615967565b9060146001600160401b03916147a86140c2565b935f525f60205260405f20906147c260ff83541686613be9565b6147ce60058301612903565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148cf600185016148a261487960408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b9261491c8161496b946080946148fd885f525f60205260405f2090565b97614909895460ff1690565b614912816123db565b156149e1576152e2565b60408101805161492b816123db565b614934816123db565b1515806149b6575b61499c575b5060148401805460608301516001600160401b03908116911681900361497a575b50500151151590565b6149725750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614962565b6149b090516149aa816123db565b856133fd565b5f614941565b50845460ff168151906149c8826123db565b6149d1826123db565b6149da816123db565b141561493c565b6149ee8260018b016147ed565b6152e2565b805f525f60205260ff60405f2054166006811015611d83578015908115614a3b575b50614a36575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a48816123db565b145f614a15565b90614aa191805f525f602052614a6a600160405f2001613218565b60a083614a86614a7f61096f60808401612cb8565b5485614794565b604051632a2d120f60e21b8152968792839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae576127af945f94614ade575b50614ad8903690612fe4565b916148e0565b614ad8919450614afc9060a03d60a011610b4c57610b3e818361277f565b9390614acc565b90614b169291614b11615622565b614b3c565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c68576001600160a01b0383169283614be0576001600160a01b038216925f8080808488620186a0f1614b73613c52565b5015614b80575050505050565b614bc3613869926125eb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bce82825461333f565b90556040519081529081906020820190565b614bf2614bee848484615afb565b1590565b614bfd575b50505050565b81614c466001600160a01b03926125eb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c5185825461333f565b90556040519384521691602090a35f808080614bf7565b505050565b905f52600560205260405f2054159081614c85575090565b6104f791506149f3565b614c976140c2565b905f5260056020526001600160401b03600360405f2060ff600182015416614cbe81611d79565b8452614ccc60058201612903565b60208501526004810154604085015201541660608201525f608082015290565b90614cf56140c2565b915f5260056020526001600160401b03600360405f2060ff600182015416614d1c81611d79565b8552614d2a60058201612903565b6020860152600481015460408601520154166060830152608082015290565b6020939291614dd491614d64815f52600560205260405f2090565b97604086018051614d7481611d79565b614d7d81611d79565b614e63575b5087856080880194614d948651151590565b614e50575b505050505060038701614db381546001600160401b031690565b60608601516001600160401b039081169116819003614e2e57505051151590565b15614e1557608001518201516001600160a01b031680935b8251905f821315614e0657614247915061423f8451615903565b5f82126143a657505050614259565b50600c84015460401c6001600160a01b03168093614dec565b815467ffffffffffffffff19166001600160401b039091161790555f806141cc565b614e5994615b68565b5f80878582614d99565b614e7a9051614e7181611d79565b60018b0161369c565b5f614d82565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f760a08261277f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615054575b806d04ee2d6d415b85acef8100000000600a921015615038575b662386f26fc10000811015615023575b6305f5e100811015615011575b612710811015615001575b6064811015614ff2575b1015614fe7575b614f7e6021614f4660018801615c26565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f8e57614f7e90614f4b565b50506001600160a01b03614fb384614fa7858498615bba565b60208151910120615c10565b911693168314614fdf57614fd1918160206114329351910120615c10565b14614fda575f90565b600190565b505050600190565b600190940193614f35565b60029060649004960195614f2e565b6004906127109004960195614f24565b6008906305f5e1009004960195614f19565b601090662386f26fc100009004960195614f0c565b6020906d04ee2d6d415b85acef81000000009004960195614efc565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ee2565b90600a811015611d835768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161515457505050565b5f5260205f20906020601f840160051c8301931061518c575b601f0160051c01905b818110615181575050565b5f8155600101615176565b909150819061516d565b91909182516001600160401b03811161082b576151bd816151b7845461282f565b84615147565b6020601f82116001146151f85781906131b09394955f926151ed575b50508160011b915f199060031b1c19161790565b015190505f806151d9565b601f1982169061520b845f5260205f2090565b915f5b8181106152455750958360019596971061522d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615223565b9192602060018192868b01518155019401920161520e565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d835760c0600d9161529c6127af958561507c565b604081015160018501556152b76060820151600286016150a9565b6152c86080820151600786016150a9565b6152d960a0820151600c8601615196565b01519101615196565b9161533160206152ff615323959694965f525f60205260405f2090565b9561531782606086015101516001600160a01b031690565b9586946005890161525d565b01516001600160a01b031690565b5f8351135f14615499576153458351615903565b6153508184846154a8565b61535f6013870191825461333f565b90555b602083019283515f8113615418575b5051905f82126153f0575b505050515f8112615393575b5050506127af61449c565b5f516020615d875f395f51905f52916153b66143236001600160a01b0393614157565b6153da601361434c8361390d866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f8080615388565b6143b56143236153ff93614157565b61540e6013850191825461334c565b9055815f8061537c565b61542190615903565b615440816142aa866001600160a01b03165f52600660205260405f2090565b908161545d866001600160a01b03165f52600660205260405f2090565b5561546d6013890191825461333f565b90556040519081526001600160a01b038416905f516020615d875f395f51905f5290602090a25f615371565b6154a33415612bff565b615362565b90614b1692916154b6615622565b908215614c68576001600160a01b03169182158015615568576154da823414612bff565b156154e457505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f5114811615615549575b6040919091525f6060521561552e5750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b600181151661555f573d15833b1515161661551c565b503d5f823e3d90fd5b6155723415612bff565b6154da565b6001015460ff1661558781611d79565b60038114908115615596575090565b600291506155a381611d79565b1490565b6001600160401b0360038201541642101590816155c2575090565b600180925060ff910154166155a381611d79565b90614b1692916155e4615622565b91908115614c68576001600160a01b03169182615619576127af92505f808080856001600160a01b0386165af161264b613c52565b6127af92615680565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156715760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156c3575b6040919091521561552e5750565b600181151661555f573d15833b151516166156b5565b905f52600260205260405f2054159081614c85575090565b6001600160401b03815116906020810151600a811015611d83576157988260406157f894015161573860806060840151930151946040519760208901526040880190611d92565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f76102408261277f565b9190915f52600260205260405f2091825560058201926158496001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d835760c06158ff9361586d6002976158b59461507c565b604081015160068701556158886060820151600788016150a9565b6158996080820151600c88016150a9565b6158aa60a082015160118801615196565b015160128501615196565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f811261590d5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061594e5750506127af9250038361277f565b8454835260019485019487945060209093019201615939565b6001600160a01b039061404b61598d61598860209895999697993690612fe4565b6156f1565b936040519889978896879563600109bb60e01b875260048701613fc8565b6001810190825f528160205260405f2054155f14615a135780546801000000000000000081101561082b57615a006159ea826001879401855584613182565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a41575f190190615a308282613182565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615af3575f198401848111611bf85783545f19810194908511611bf8575f958583615ab097615aa39503615ab6575b505050615a1a565b905f5260205260405f2090565b55600190565b615adc615ad691615acd6105e3615aea9588613182565b92839187613182565b90613197565b85905f5260205260405f2090565b555f8080615a9b565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b3960648261277f565b51908285620186a0f15f51913d9115615a13578115615b5f5750602011614a3657151590565b9150503b151590565b9190915f52600560205260405f2091825560058201926158496001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b6127af90615c02615bfc94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615ba8565b90615ba8565b03601f19810184528361277f565b6104f791615c1d91615c4e565b90929192615c88565b90615c3082612f78565b615c3d604051918261277f565b8281528092613335601f1991612f78565b8151919060418303615c7e57615c779250602082015190606060408401519301515f1a90615d04565b9192909190565b50505f9160029190565b615c9181611d79565b80615c9a575050565b615ca381611d79565b60018103615cba5763f645eedf60e01b5f5260045ffd5b615cc381611d79565b60028103615cde575063fce698f760e01b5f5260045260245ffd5b80615cea600392611d79565b14615cf25750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d7b579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104ae575f516001600160a01b03811615615d7157905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212205e68caa4b2d471ba6fc8e7a2cb4b8350ab1068b01b622da34e2d2daf0aa4dd6064736f6c634300081e0033000000000000000000000000708b3ca8b7dc0f89ea5a06709c3b92dd5843b6620000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0x43", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x144df82", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xd74da343a9f1079e3455d9d62396bb9b807a6b8e93e9727cf30c7f91adec5def", + "transactionIndex": "0x62", + "blockHash": "0x58c253949c6cbccf8e6869ab89fd94dba1df9cc9b56ff6d0b1287d7cda4e2cfa", + "blockNumber": "0xa61bc6", + "gasUsed": "0x506f18", + "effectiveGasPrice": "0x40deca7f", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x7d61ec428cfae560f43647af567ea7c6e2cc5527" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779286321195, + "chain": 11155111, + "commit": "104c13df" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779353692222.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779353692222.json new file mode 100644 index 000000000..68d482b0c --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779353692222.json @@ -0,0 +1,58 @@ +{ + "transactions": [ + { + "hash": "0xf80e9796a40d5b976366430516408e7240f211b769159a65fcb9aed030bfd75f", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xac00326e2d0c33253d77e22034291f4c2b6f3681", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": null, + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x6c333ac983749b9404704915ee6859bf1942a091", + "function": null, + "arguments": [ + "0xAc00326E2D0C33253d77E22034291F4c2b6F3681", + "0xc76632D91D45Ec88304ab2a983451d9EDf908C0d" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x686f87", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000ac00326e2d0c33253d77e22034291f4c2b6f3681000000000000000000000000c76632d91d45ec88304ab2a983451d9edf908c0d", + "nonce": "0x1", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [ + "0xf80e9796a40d5b976366430516408e7240f211b769159a65fcb9aed030bfd75f" + ], + "returns": {}, + "timestamp": 1779353692222, + "chain": 11155111, + "commit": "5922d170" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779648336659.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779648336659.json new file mode 100644 index 000000000..b40baf03d --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1779648336659.json @@ -0,0 +1,89 @@ +{ + "transactions": [ + { + "hash": "0x581be27b4d4b12595edd08dd50e3918e120bc78d2608cc359f85584d7896c576", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa15ebf0cadb7273349070952636edfb166053f09f6e52fe57e73bfa02e1ce54a", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x16d9798", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x581be27b4d4b12595edd08dd50e3918e120bc78d2608cc359f85584d7896c576", + "transactionIndex": "0xbf", + "blockHash": "0x31d115e626acd302eb008c312c8ba101352b8c18a849e1b34f88d0d3be6e18c0", + "blockNumber": "0xa687c3", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x4241e512", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1c2db2b", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa15ebf0cadb7273349070952636edfb166053f09f6e52fe57e73bfa02e1ce54a", + "transactionIndex": "0xc5", + "blockHash": "0x31d115e626acd302eb008c312c8ba101352b8c18a849e1b34f88d0d3be6e18c0", + "blockNumber": "0xa687c3", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x4241e512", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779648336659, + "chain": 11155111, + "commit": "b88d511c" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json index 5af57197a..eebe938d7 100644 --- a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json @@ -1,92 +1,41 @@ { "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", + "hash": "0x581be27b4d4b12595edd08dd50e3918e120bc78d2608cc359f85584d7896c576", "transactionType": "CREATE", "contractName": "ECDSAValidator.channelhub", - "contractAddress": "0x735eb1026afba78b602da39c6b59eaba95753686", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", "function": null, "arguments": null, "transaction": { - "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", - "gas": "0x880d5", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x84c87", "value": "0x0", - "input": "0x608080604052346015576106d6908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63600109bb14610024575f80fd5b346100cc5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100cc5760243567ffffffffffffffff81116100cc576100739036906004016100d0565b9060443567ffffffffffffffff81116100cc576100949036906004016100d0565b6064359173ffffffffffffffffffffffffffffffffffffffff831683036100cc576020946100c4946004356101a0565b604051908152f35b5f80fd5b9181601f840112156100cc5782359167ffffffffffffffff83116100cc57602083818601950101116100cc57565b90601f601f19910116810190811067ffffffffffffffff82111761012157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161012157601f01601f191660200190565b9291926101768261014e565b9161018460405193846100fe565b8294818452818301116100cc578281602093845f960137010152565b929091949383156102635773ffffffffffffffffffffffffffffffffffffffff85161561023b5761022060806101de6102279561022d99369161016a565b95601f19601f6020604051998a94828601526040808601528051918291826060880152018686015e5f858286010152011681010301601f1981018652856100fe565b369161016a565b9061028b565b1561023757600190565b5f90565b7f4501a919000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe1b97cf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156104cc575b806d04ee2d6d415b85acef8100000000600a9210156104b1575b662386f26fc1000081101561049d575b6305f5e10081101561048c575b61271081101561047d575b606481101561046f575b1015610465575b6001850190600a602161033461031e8561014e565b9461032c60405196876100fe565b80865261014e565b97601f19602086019901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530490811561038057600a90610345565b505073ffffffffffffffffffffffffffffffffffffffff5f9361040c86610415946020610404869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f1981018352826100fe565b5190206104f4565b9094919461052e565b169416841461045c5773ffffffffffffffffffffffffffffffffffffffff9261044d92610444925190206104f4565b9092919261052e565b1614610457575f90565b600190565b50505050600190565b9360010193610309565b606460029104960195610302565b612710600491049601956102f8565b6305f5e100600891049601956102ed565b662386f26fc10000601091049601956102e0565b6d04ee2d6d415b85acef8100000000602091049601956102d0565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046102b6565b81519190604183036105245761051d9250602082015190606060408401519301515f1a90610606565b9192909190565b50505f9160029190565b60048110156105d95780610540575050565b60018103610570577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b600281036105a457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003146105ae5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610695579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561068a575f5173ffffffffffffffffffffffffffffffffffffffff81161561068057905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea2646970667358221220c8e32dfe4c3317faffb02d4b02fddbb5e01dbc789e117442dd5ec08557786de764736f6c634300081e0033", - "nonce": "0x2b", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", "chainId": "0xaa36a7" }, "additionalContracts": [], "isFixedGasLimit": false }, { - "hash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", + "hash": "0xa15ebf0cadb7273349070952636edfb166053f09f6e52fe57e73bfa02e1ce54a", "transactionType": "CREATE", "contractName": "ChannelHub", - "contractAddress": "0xb7be0e2007ddf320d680942cb9e008f986e74f83", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", "function": null, "arguments": [ - "0x735EB1026aFbA78B602dA39C6B59EABa95753686" + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" ], "transaction": { - "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", - "gas": "0x6a626e", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x686f97", "value": "0x0", - "input": "0x60a0346100aa57601f61608238819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615fbf90816100c382396080518181816111420152613f180152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806316b390b11461024457806317536c061461023f578063187576d81461023a5780633115f6301461023557806338a66be21461023057806341b660ef1461022b57806347de477a146102265780635326919814610221578063587675e81461021c5780635a0745b4146102175780635b9acbf9146102125780635dc46a741461020d5780636840dbd2146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f457806382d3e15d146101ef5780638d0b12a5146101ea57806394191051146101e55780639691b468146101e0578063a5c82680146101db578063b00b6fd6146101d6578063b25a1d38146101d1578063beed9d5f146101cc578063c74a2d10146101c7578063d888ccae146101c2578063dc23f29e146101bd578063dd73d494146101b8578063e617208c146101b3578063ecf3d7e8146101ae578063f4ac51f5146101a9578063f766f8d6146101a4578063ff5bc09e1461019f5763ffa1ad741461019a575f80fd5b612650565b612639565b61249a565b61241f565b61230d565b61226e565b6120f0565b611ef5565b611db5565b611b71565b6119d6565b6116c4565b61165b565b6114ba565b611379565b61135c565b6111cb565b6111ae565b611171565b61112d565b611112565b611026565b61100f565b610fc8565b610fa6565b610f8a565b610f44565b610cfc565b610b13565b610850565b6107ea565b61065e565b6105d8565b6104ca565b6102cb565b9181601f840112156102775782359167ffffffffffffffff8311610277576020838186019501011161027757565b5f80fd5b60643590600282101561027757565b90606060031983011261027757600435916024359067ffffffffffffffff8211610277576102ba91600401610249565b909160443560028110156102775790565b34610277576102d93661028a565b6103986102f1859493945f52600260205260405f2090565b9283546102ff81151561266b565b61035a600286019461032a61031b87546001600160a01b031690565b948560038a019a8b5492613eff565b9591600160068b019a019661034a88546001600160a01b039060081c1690565b926103548c6128a0565b8861405d565b60c061036588614161565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301612a25565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80196610436956080955f9461044b575b50836104146104066104279697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104208a6128a0565b908c61428d565b015167ffffffffffffffff1690565b9061044660405192839283612a41565b0390a2005b6104279450946104146104786104069760c03d60c011610481575b6104708183612707565b810190612950565b955050946103e8565b503d610466565b612a36565b6001600160a01b0381160361027757565b6003196060910112610277576004356104b68161048d565b906024356104c38161048d565b9060443590565b6001600160a01b036104db3661049e565b92909116906104eb821515612b9e565b6104f6831515612bcd565b815f52600660205261051c8160405f20906001600160a01b03165f5260205260405f2090565b80549184830180931161059a577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b03925561055d615810565b61056885823361458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b612bfc565b60206040818301928281528451809452019201905f5b8181106105c25750505090565b82518452602093840193909201916001016105b5565b34610277576020600319360112610277576001600160a01b036004356105fd8161048d565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b818110610648576106448561063881870382612707565b6040519182918261059f565b0390f35b8254845260209093019260019283019201610621565b3461027757602060031936011261027757600354600480549190355f5b828410806107e1575b156107d4576106b06106a2610698866131a0565b90549060031b1c90565b5f52600260205260405f2090565b6001810160036106c1825460ff1690565b6106ca81611c00565b146107c2576106d882615b4d565b1561077e57915f8261076961077595600561076f96019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254612c10565b9055600360ff19825416179055565b556139c6565b936139c6565b915b919261067b565b505092905061078d9150600455565b8061079457005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b5050926107ce906139c6565b91610777565b92905061078d9150600455565b50818110610684565b34610277575f600319360112610277576020604051620186a08152f35b90816102609103126102775790565b90600319820160e081126102775760c0136102775760049160c4359067ffffffffffffffff82116102775761084d91600401610807565b90565b61085936610816565b906020820191600261086a84612c27565b61087381611c0f565b148015610af8575b8015610ada575b61088b90612c31565b61091a6108a061089b3685612c79565b614780565b916108aa8461483e565b60208401906108b882612ced565b956108d760408701976108ca89612ced565b608089013591858961494b565b60c0826108ff6108f86108ec6107148c612ced565b61073d60808501612ced565b54886149c5565b6040519687928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af493841561048857610a156001600160a01b0394610a2d936109967fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a1b955f91610aab575b50610985368d612c79565b61098f368a61301c565b908c614b52565b6109c2896109bd6109a685612ced565b6001600160a01b03165f52600160205260405f2090565b615bde565b5060026109ce82612c27565b6109d781611c0f565b03610a325750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a0d89826130ca565b0390a2612ced565b97612ced565b918360405194859416981696836130db565b0390a4005b610a3d600391612c27565b610a4681611c0f565b03610a7b57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a0d89826130ca565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a0d89826130ca565b610acd915060c03d60c011610ad3575b610ac58183612707565b810190612cf7565b5f61097a565b503d610abb565b5061088b610ae784612c27565b610af081611c0f565b159050610882565b506003610b0484612c27565b610b0d81611c0f565b1461087b565b610b1c36610816565b90610b3d6004610b2e60208501612c27565b610b3781611c0f565b14612c31565b610b4a61089b3683612c79565b9160208201610b5881612ced565b90610b7960408501926080610b6c85612ced565b960135958691868961494b565b610b8b610b858461316b565b86614c6c565b93610b9586614c9c565b15610bdd57505050610bd881610bcc7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f9809386614cf9565b604051918291826130ca565b0390a3005b610c259060c085610bf18897959697614161565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401613175565b038173728904e52308213ba61c90ef49f34c18fbda9e115af48015610488577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610bd895610c9a945f93610ca3575b50610c82610c8891612ced565b91612ced565b91610c93368761301c565b8a8a61428d565b610bcc846131ef565b610c88919350610cc4610c829160c03d60c011610481576104708183612707565b939150610c75565b90604060031983011261027757600435916024359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610d0a36610ccc565b610d1b6009610b2e60208401612c27565b610d376001610d31845f525f60205260405f2090565b0161323d565b610dfd610d4e60208301516001600160a01b031690565b9161071460c060408301610d7a610d6c82516001600160a01b031690565b608086015190888a8c61494b565b610de2610ddb610dc4610d8d368b61301c565b9586946101408c018d8d610da08361316b565b67ffffffffffffffff1646149d8e610eb7575b50505050516001600160a01b031690565b6060840151602001516001600160a01b031661073d565b54896149c5565b6040519586928392632a2d120f60e21b8452600484016132c9565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857610e2f935f93610e96575b5086614b52565b15610e65576104467f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c391604051918291826130ca565b6104467f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c82691604051918291826130ca565b610eb091935060c03d60c011610ad357610ac58183612707565b915f610e28565b610edb610f1092610ecc610f15963690612f3c565b60608d01526060369101612f3c565b60808b0152610ee86132b5565b60a08b0152610ef56132b5565b8b8b01526001600160a01b03165f52600160205260405f2090565b615c88565b505f8d8d82610db3565b600319604091011261027757600435610f378161048d565b9060243561084d8161048d565b34610277576020610f816001600160a01b03610f5f36610f1f565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610277575f600319360112610277576020604051612a308152f35b3461027757604060031936011261027757610644610638602435600435613373565b610fda610fd436610ccc565b9061342d565b005b60606003198201126102775760043591602435916044359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610fda61102036610fdc565b9161378a565b34610277576020600319360112610277576001600160a01b0360043561104b8161048d565b165f52600160205261105f60405f20615b05565b5f905f5b81518110156110ff5761109161108a61107c838561335f565b515f525f60205260405f2090565b5460ff1690565b61109a816121c1565b600381141590816110ea575b506110b4575b600101611063565b916110c78184600193106110cf576139c6565b9290506110ac565b6110d9858561335f565b516110e4828661335f565b526139c6565b600591506110f7816121c1565b14155f6110a6565b506106449181526040519182918261059f565b34610277575f60031936011261027757602060405160408152f35b34610277575f600319360112610277576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610277576020610f816001600160a01b0361118c36610f1f565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610277575f600319360112610277576020600454604051908152f35b34610277576112556111dc3661028a565b929391906111f2855f52600560205260405f2090565b9182549261120184151561266b565b600281019060a061122261121c84546001600160a01b031690565b8a615053565b604051809881927f24063eba000000000000000000000000000000000000000000000000000000008352600483016139d4565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4958615610488575f9661132b575b50600181015460081c6001600160a01b0316968792546112a2906001600160a01b031690565b809581956003850154976112b7928992613eff565b9a9190946006019a6112c88c6128a0565b956112d3968b61405d565b846112dd876128a0565b6112e795896150bb565b6060015167ffffffffffffffff166040519182916113059183612a41565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61134e91965060a03d60a011611355575b6113468183612707565b8101906136bc565b945f61127c565b503d61133c565b34610277575f600319360112610277576020604051620151808152f35b61143661138536610ccc565b6113a661139760208395949501612c27565b6113a081611c0f565b15612c31565b6113bc6001610d31855f525f60205260405f2090565b9060c08161141b6114146108ec6107146113e060208901516001600160a01b031690565b6114078b8a60408101938960806113fe87516001600160a01b031690565b9301519361494b565b516001600160a01b031690565b54876149c5565b6040519586928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361044693610bcc925f92611499575b50611492368561301c565b9087614b52565b6114b391925060c03d60c011610ad357610ac58183612707565b905f611487565b34610277576114c836610816565b906114da6006610b2e60208501612c27565b6114e761089b3683612c79565b91602082016114f581612ced565b9061150960408501926080610b6c85612ced565b611515610b858461316b565b9361151f86614c9c565b1561155657505050610bd881610bcc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614cf9565b6115a59060a08561157261156c87989697612ced565b89615053565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613773565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af48015610488577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610bd895610bcc945f93611614575b50610c8261160291612ced565b9161160d368761301c565b8a8a6150bb565b611602919350611635610c829160a03d60a011611355576113468183612707565b9391506115f5565b6024359060ff8216820361027757565b359060ff8216820361027757565b34610277576040600319360112610277576001600160a01b036116a96004356116838161048d565b8261168c61163d565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102775760043560243567ffffffffffffffff8111610277576116f3903690600401610807565b60443567ffffffffffffffff811161027757611713903690600401610249565b919061171d61027b565b9061172f855f525f60205260405f2090565b9161173c6001840161323d565b9161176661174b855460ff1690565b611754816121c1565b600181149081156119c2575b506139e5565b86611773600586016128a0565b916117b46117808861316b565b67ffffffffffffffff6117ab61179e875167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613a14565b60208501516001600160a01b0316976117d760408701516001600160a01b031690565b9367ffffffffffffffff6117ff61179e6117f08c61316b565b935167ffffffffffffffff1690565b9116116118c3575b94611867889795857f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9b6118616118859760149d61185561187c996118959c6118b49f60808c015192613eff565b9391949092369061301c565b9061405d565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613a43565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b61044660405192839283613a65565b909296959397946118df61190a9389888a60808601519361494b565b60c08761141b6119036108ec8c6001600160a01b03165f52600660205260405f2090565b548d6149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4938415610488577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118b4986118618e8c61185560149f976118679861187c9b61198a6118959f6118859f5f916119a3575b508d611983368961301c565b9089615443565b9a9f5050995050509750509b5095509597985050611807565b6119bc915060c03d60c011610ad357610ac58183612707565b5f611977565b600491506119cf816121c1565b145f611760565b34610277576080600319360112610277576004356119f38161048d565b6119fb61163d565b90604435611a088161048d565b60643567ffffffffffffffff811161027757611b4a6001600160a01b0392611b22611a6396611b07611b02611a4289973690600401610249565b60ff85169a91611afc90611a578d1515613a8d565b8b89169d8e1515612b9e565b611abf8785611ab9611aad611aad611aa085611a90866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613abc565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611af5608085612707565b3691612fcb565b9061564c565b613b01565b611a90856001600160a01b03165f52600760205260405f2090565b906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611b91611b7d36610ccc565b6113a66003610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361044693610bcc925f926114995750611492368561301c565b634e487b7160e01b5f52602160045260245ffd5b60041115611c0a57565b611bec565b600a1115611c0a57565b90600a821015611c0a5752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61084d9167ffffffffffffffff8251168152611c6f60208301516020830190611c19565b60408201516040820152611cdd6060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611d5f60a0840151610260610220850152610260840190611c26565b92015190610240818403910152611c26565b929367ffffffffffffffff60c09561084d98979482948752611d9281611c00565b602087015216604085015216606083015260808201528160a08201520190611c4b565b3461027757602060031936011261027757600435611dd1613b66565b505f52600260205260405f20611de561272a565b9080548252610644600182015491611e31611e21611e038560ff1690565b94611e12602088019687613baa565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e58611e4860028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611e90905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611ee46117f0611ec0600660058501549460e08701958652016128a0565b93610100810194855251965197611ed689611c00565b5167ffffffffffffffff1690565b905191519260405196879687611d71565b3461027757611f0336610816565b611f146008610b2e60208401612c27565b80611f89611f2561089b3686612c79565b936020810160c0611f3582612ced565b91611f546040850193611f4785612ced565b608087013591898c61494b565b610de2610ddb610dc4610714611f6a368b61301c565b9687958d8a611f7882614c9c565b9d8e15612052575b50505050612ced565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857611fc6935f9361202d575b50611fc0903690612c79565b86614b52565b15611ffc576104467f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a91604051918291826130ca565b6104467f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b891604051918291826130ca565b611fc091935061204b9060c03d60c011610ad357610ac58183612707565b9290611fb4565b6120aa936120876109a6926120696109bd9561483e565b8c606061207a366101408501612f3c565b9101526060369101612f3c565b60808c01526120946132b5565b60a08c01526120a16132b5565b8c8c0152612ced565b505f8d8a8e611f80565b9160a09367ffffffffffffffff9161084d97969385526120d381611c00565b602085015216604083015260608201528160808201520190611c4b565b346102775760206003193601126102775760043561210c613b66565b505f52600560205260405f2061212061273c565b908054825261064460018201549161213e611e21611e038560ff1690565b612155611e4860028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121b061219b600660058501549460c08501958652016128a0565b9160e0810192835251945195611ed687611c00565b9151905191604051958695866120b4565b60061115611c0a57565b906006821015611c0a5752565b9192612250610120946121f285612263959a99989a6121cb565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c4b565b946101008201520152565b34610277576020600319360112610277576004355f60a0604051612291816126ae565b82815282602082015282604082015282606082015282608082015201526122b6613b66565b505f525f6020526122c960405f20613bc2565b80516122d4816121c1565b61064460208301519260408101519060606122fd61179e608084015167ffffffffffffffff1690565b91015191604051958695866121d8565b346102775761231b3661049e565b90916123316001600160a01b0382161515612b9e565b61233c821515612bcd565b335f5260066020526123628360405f20906001600160a01b03165f5260205260405f2090565b54908282106123f75782820391821161059a578383916123b7936123b18361239b336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55614d9d565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610bd83394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b61243f61242b36610ccc565b6113a66002610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361044693610bcc925f926114995750611492368561301c565b34610277576124a836610f1f565b6124b0615810565b6001600160a01b038116916124c6831515612b9e565b6001600160a01b036124ed8261239b336001600160a01b03165f52600860205260405f2090565b54916124fa831515612bcd565b5f61251a8261239b336001600160a01b03165f52600860205260405f2090565b55169181836125945761253d915f808080858a5af1612537613c20565b50613c4f565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610fda60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f511481161561261a575b60409190915261253d577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612630573d15843b151516166125db565b503d5f823e3d90fd5b3461027757610fda61264a36610fdc565b91613c90565b34610277575f60031936011261027757602060405160018152f35b1561267257565b7fc60f1e78000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176126ca57604052565b61269a565b60e0810190811067ffffffffffffffff8211176126ca57604052565b60a0810190811067ffffffffffffffff8211176126ca57604052565b90601f601f19910116810190811067ffffffffffffffff8211176126ca57604052565b6040519061273a61012083612707565b565b6040519061273a61010083612707565b6040519061273a60e083612707565b90604051612768816126cf565b60c0600482946127a660ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c921680156127f9575b60208310146127e557565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127da565b5f9291815491612812836127cb565b8083529260018116908115612867575060011461282e57505050565b5f9081526020812093945091925b83831061284d575060209250010190565b60018160209294939454838587010152019101919061283c565b9050602094955060ff1991509291921683830152151560051b010190565b9061273a6128999260405193848092612803565b0383612707565b906040516128ad816126cf565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c0a57600d61291f9160c0936020860152600181015460408601526128f26002820161275b565b60608601526129036007820161275b565b6080860152612914600c8201612885565b60a086015201612885565b910152565b5190600482101561027757565b67ffffffffffffffff81160361027757565b5190811515820361027757565b908160c0910312610277576129b860a06040519261296d846126ae565b805184526020810151602085015261298760408201612924565b6040850152606081015161299a81612931565b606085015260808101516129ad81612931565b608085015201612943565b60a082015290565b9081516129cc81611c00565b815260a0806129ea602085015160c0602086015260c0850190611c4b565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b90602061084d9281815201906129c0565b6040513d5f823e3d90fd5b92916020612b8d61273a9360408752612a75815467ffffffffffffffff811660408a015260ff60608a019160401c16611c19565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d612b5b6102a08901600c8401612803565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612803565b94019067ffffffffffffffff169052565b15612ba557565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b15612bd457565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161059a57565b600a111561027757565b3561084d81612c1d565b15612c3857565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b63ffffffff81160361027757565b359061273a82612931565b91908260c091031261027757604051612c91816126ae565b60a08082948035612ca181612c60565b84526020810135612cb18161048d565b60208501526040810135612cc48161048d565b60408501526060810135612cd781612931565b6060850152608081013560808501520135910152565b3561084d8161048d565b908160c09103126102775760405190612d0f826126ae565b805182526020810151602083015260408101516006811015610277576129b89160a09160408501526060810151612d4581612931565b60608501526129ad60808201612943565b90612d628183516121cb565b608067ffffffffffffffff81612d87602086015160a0602087015260a0860190611c4b565b94604081015160408601526060810151606086015201511691015290565b359061273a82612c1d565b60c0809167ffffffffffffffff8135612dc881612931565b1684526001600160a01b036020820135612de18161048d565b16602085015260ff612df56040830161164d565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561027757016020813591019167ffffffffffffffff821161027757813603831361027757565b601f8260209493601f1993818652868601375f8582860101520116010190565b61084d9167ffffffffffffffff8235612e8a81612931565b168152612ea86020830135612e9e81612c1d565b6020830190611c19565b60408201356040820152612ec26060820160608401612db0565b612ed461014082016101408401612db0565b612f08612efc612ee8610220850185612e20565b610260610220860152610260850191612e52565b92610240810190612e20565b91610240818503910152612e52565b9091612f2e61084d93604084526040840190612d56565b916020818403910152612e72565b91908260e091031261027757604051612f54816126cf565b60c08082948035612f6481612931565b84526020810135612f748161048d565b6020850152612f856040820161164d565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116126ca57601f01601f191660200190565b929192612fd782612faf565b91612fe56040519384612707565b829481845281830111610277578281602093845f960137010152565b9080601f830112156102775781602061084d93359101612fcb565b919091610260818403126102775761303261274c565b9261303c82612c6e565b845261304a60208301612da5565b6020850152604082013560408501526130668160608401612f3c565b6060850152613079816101408401612f3c565b608085015261022082013567ffffffffffffffff8111610277578161309f918401613001565b60a085015261024082013567ffffffffffffffff8111610277576130c39201613001565b60c0830152565b90602061084d928181520190612e72565b60e09060a061084d949363ffffffff81356130f581612c60565b1683526001600160a01b03602082013561310e8161048d565b1660208401526001600160a01b03604082013561312a8161048d565b16604084015267ffffffffffffffff606082013561314781612931565b16606084015260808101356080840152013560a08201528160c08201520190612e72565b3561084d81612931565b9091612f2e61084d936040845260408401906129c0565b634e487b7160e01b5f52603260045260245ffd5b6003548110156131b85760035f5260205f2001905f90565b61318c565b80548210156131b8575f5260205f2001905f90565b916131eb9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156126ca57600181016003556003548110156131b85760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b9060405161324a816126ae565b60a0600382946001600160a01b03815463ffffffff8116865260201c1660208501526132a467ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906132c4602083612707565b5f8252565b90916132e061084d93604084526040840190612d56565b916020818403910152611c4b565b67ffffffffffffffff81116126ca5760051b60200190565b60405190613315602083612707565b5f808352366020840137565b9061332b826132ee565b6133386040519182612707565b828152601f1961334882946132ee565b0190602036910137565b9190820391821161059a57565b80518210156131b85760209160051b010190565b9190600354908084029380850482149015171561059a57818410156133f75783019081841161059a578082116133ef575b506133b76133b28483613352565b613321565b92805b8281106133c657505050565b806133d56106986001936131a0565b6133e86133e28584613352565b8861335f565b52016133ba565b90505f6133a4565b5050905061084d613306565b906006811015611c0a5760ff60ff198354169116179055565b90602061084d928181520190611c4b565b9061343f825f525f60205260405f2090565b61344b6001820161323d565b91613457825460ff1690565b9184613465600583016128a0565b91604086019261347c84516001600160a01b031690565b91600261349360208a01516001600160a01b031690565b9761349d816121c1565b1480613654575b6135995750506134e16108f86108ec6107146134fc9661140760c0978a978c898f6080906134d96001610b2e60208601612c27565b01519361494b565b6040519384928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4801561048857610f10613573946109a688937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613566965f92613578575b5061355f368961301c565b9086614b52565b50604051918291826130ca565b0390a2565b61359291925060c03d60c011610ad357610ac58183612707565b905f613554565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613573969195506136479450610f10926135fe6014836135e66109a695600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016136278151606061361d60208301516001600160a01b031690565b9101519086614d9d565b5160a061363e60208301516001600160a01b031690565b91015191614d9d565b506040519182918261341c565b5061366d61179e601483015467ffffffffffffffff1690565b42116134a4565b1561367b57565b7fdb1ea1ac000000000000000000000000000000000000000000000000000000005f5260045ffd5b906136ad81611c00565b60ff60ff198354169116179055565b908160a0910312610277576137116080604051926136d9846126eb565b80518452602081015160208501526136f360408201612924565b6040850152606081015161370681612931565b606085015201612943565b608082015290565b90815161372581611c00565b815260806001600160a01b038161374b602086015160a0602087015260a0860190611c4b565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612f2e61084d93604084526040840190613719565b9161379483614c9c565b613959576137aa825f52600560205260405f2090565b6137b684825414613674565b60018101918254916137d2836001600160a01b039060081c1690565b9360026137f26137eb828501546001600160a01b031690565b9560ff1690565b6137fb81611c00565b1480613939575b6138bf5750600361383b9161381e6007610b2e60208701612c27565b019261382e84548287868b61494b565b60a0836115728389615053565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4908115610488577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19561389995610bcc945f9461389e575b50549261160d368761301c565b0390a3565b6138b891945060a03d60a011611355576113468183612707565b925f61388c565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1945090613899936138fb610bcc93600360ff19825416179055565b613933600d60058401935f855495556139226004820167ffffffffffffffff198154169055565b015460401c6001600160a01b031690565b90614d9d565b5061395261179e600484015467ffffffffffffffff1690565b4211613802565b6138997f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d49891610bcc613992865f525f60205260405f2090565b6139be60026139af60018401546001600160a01b039060201c1690565b9201546001600160a01b031690565b908388615025565b5f19811461059a5760010190565b90602061084d928181520190613719565b156139ec57565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613a1b57565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161059a57565b9067ffffffffffffffff613a86602092959495604085526040850190612e72565b9416910152565b15613a9457565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613ac5575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613b0857565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613b3d826126cf565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b73826126cf565b606060c0835f81525f60208201525f6040820152613b8f613b30565b83820152613b9b613b30565b60808201528260a08201520152565b613bb382611c00565b52565b6006821015611c0a5752565b90604051613bcf816126eb565b608067ffffffffffffffff60148395613bec60ff82541686613bb6565b613bf86001820161323d565b6020860152613c09600582016128a0565b604086015260138101546060860152015416910152565b3d15613c4a573d90613c3182612faf565b91613c3f6040519384612707565b82523d5f602084013e565b606090565b15613c58575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b91613c9a83614c9c565b613e8157613cb0825f52600260205260405f2090565b613cbc84825414613674565b6001810191825491613cd8836001600160a01b039060081c1690565b936002613cf16137eb828501546001600160a01b031690565b613cfa81611c00565b1480613e5e575b613de457506003613d6591613d1d6005610b2e60208701612c27565b0192613d2d84548287868b61494b565b60c083610bf1613d5e613d51856001600160a01b03165f52600660205260405f2090565b61073d6101608501612ced565b5489614206565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9561389995610bcc945f94613dc3575b505492610c93368761301c565b613ddd91945060c03d60c011610481576104708183612707565b925f613db6565b7f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e94509061389993613e20610bcc93600360ff19825416179055565b613933600d60058401935f85549555613922600482017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b506004820154613e7a9060401c67ffffffffffffffff1661179e565b4211613d01565b6138997f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c91610bcc613992865f525f60205260405f2090565b15613ec3575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215613fd157843560f81c9081613f4c57507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150613f489050565b9091565b600180915f97939594975060ff86161c1603613fa957613f9d83613f8b611aa0613f4896611a908a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515613eba565b600101915f1990910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c0a57565b90816020910312610277575190565b939260609361404f6001600160a01b0394613a86949998998852608060208901526080880190611c26565b918683036040880152612e52565b906001600160a01b03929560209761409195996140c861407f61410d95615887565b6140ba604051998a928e840190613ff9565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f198101895288612707565b6140d18161400b565b61415a57505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b0392165afa80156104885761273a915f9161412b575b501515613b01565b61414d915060203d602011614153575b6141458183612707565b810190614015565b5f614123565b503d61413b565b90506140d7565b5f6040519161416f836126ae565b81835261420260208401614181613b66565b81526141f46040860191858352611e806004606089019288845260808a01958987526141bc60a08c01998b8b525f52600260205260405f2090565b9160ff6001840154166141ce81611c00565b8c526141dc600684016128a0565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614214836126ae565b5f835261420260208401614226613b66565b81526141f460408601915f8352611e80600460608901925f845260808a01955f87526141bc60a08c01995f8b525f52600260205260405f2090565b7f8000000000000000000000000000000000000000000000000000000000000000811461059a575f0390565b6020939291614329919796976142ab815f52600260205260405f2090565b976040860180516142bb81611c00565b6142c481611c00565b61450a575b5089888660a08901956142dc8751151590565b6144f6575b5050505050506142fc606085015167ffffffffffffffff1690565b67ffffffffffffffff81166144cb575b50608084015167ffffffffffffffff168061447c575b5051151590565b1561446357608001518201516001600160a01b031680935b8251905f82131561442357614363915061435b8451615ad0565b928391614527565b61437260058601918254612c10565b90555b0180515f8113156143d15750916143b060059261239b6143986143c69651615ad0565b966001600160a01b03165f52600660205260405f2090565b6143bb858254613352565b905501918254612c10565b90555b61273a61467e565b9290505f83126143e5575b505050506143c9565b61440260059261239b6143986143fd61441897614261565b615ad0565b61440d858254612c10565b905501918254613352565b90555f8080806143dc565b5f8212614433575b505050614375565b6144426143fd61444a93614261565b928391614d9d565b61445960058601918254613352565b9055825f8061442b565b50600d84015460401c6001600160a01b03168093614341565b6144c59060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614322565b6144f090600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f61430c565b6144ff956159a2565b5f80898886836142e1565b614521905161451881611c00565b60018b016136a3565b5f6142c9565b9061453a9291614535615810565b61458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b1561456757565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614679576001600160a01b0316918215801561466a576145b3823414614560565b156145bd57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614654575b6040919091525f606052156146205750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612630573d15833b1515161661460e565b6146743415614560565b6145b3565b505050565b6003546004545f5b82821080614776575b1561476b576146a36106a2610698846131a0565b6001810160036146b4825460ff1690565b6146bd81611c00565b14614759576146cb82615b4d565b1561471657915f8261076961470d95600561470796019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b916139c6565b915b9190614686565b5050915061472390600455565b8061472b5750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614765906139c6565b9161470f565b915061472390600455565b506040811061468f565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f01000000000000000000000000000000000000000000000000000000000000009160405161482760208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c0815261483660e082612707565b519020161790565b602081013561484c8161048d565b6001600160a01b03811690614862821515612b9e565b6040830135906148718261048d565b6148986001600160a01b0383169261488a841515612b9e565b6148938361048d565b61048d565b6148a18161048d565b5081146148ed575063ffffffff6201518091356148bd81612c60565b16106148c557565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b903590601e1981360301821215610277570180359067ffffffffffffffff82116102775760200191813603831361027757565b929161273a9461497c61498b92614971838761496b610220890189614918565b90613eff565b90878a949394615b81565b8361496b610240850185614918565b92909194615b81565b604051906149a1826126eb565b5f6080838281526149b0613b66565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916149da614994565b935f525f60205260405f20906149f460ff83541686613bb6565b614a00600583016128a0565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b169116178455614b4160018501614aef614ac660408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b92614b8e81614bde9460a094614b6f885f525f60205260405f2090565b97614b7b895460ff1690565b614b84816121c1565b15614c5a57615443565b604081018051614b9d816121c1565b614ba6816121c1565b151580614c2f575b614c15575b50601484018054606083015167ffffffffffffffff9081169116819003614bed575b50500151151590565b614be55750565b60135f910155565b614c0e919067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f80614bd5565b614c299051614c23816121c1565b85613403565b5f614bb3565b50845460ff16815190614c41826121c1565b614c4a826121c1565b614c53816121c1565b1415614bae565b614c678260018b01614a1f565b615443565b9067ffffffffffffffff604051916020830193845216604082015260408152614c96606082612707565b51902090565b805f525f60205260ff60405f2054166006811015611c0a578015908115614ce5575b50614ce0575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150614cf2816121c1565b145f614cbe565b90614d3b91614d146001610d31835f525f60205260405f2090565b60c0836108ff614d346108ec61071460408701516001600160a01b031690565b54856149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af49283156104885761273a945f94614d78575b50614d7290369061301c565b91614b52565b614d72919450614d969060c03d60c011610ad357610ac58183612707565b9390614d66565b9061453a9291614dab615810565b9190918115614679576001600160a01b0383169283614e4f576001600160a01b038216925f8080808488620186a0f1614de2613c20565b5015614def575050505050565b614e326138999261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614e3d828254612c10565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa908115610488575f91615006575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f91829190614ee7606482612707565b51908286620186a0f190614ef9613c20565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104885786915f93614fe5575b5083614fda575b83614fc6575b50505015614f5b575b50505050565b81614fa46001600160a01b039261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614faf858254612c10565b90556040519384521691602090a35f808080614f55565b614fd1929350613352565b145f8481614f4c565b818110159350614f46565b614fff91935060203d602011614153576141458183612707565b915f614f3f565b61501f915060203d602011614153576141458183612707565b5f614e92565b909192614d14614d3b946150456001610d31865f525f60205260405f2090565b92608084015191868661494b565b906001600160a01b0390615065614994565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661508d81611c00565b865261509b600682016128a0565b602087015260058101546040870152015416606084015216608082015290565b6020939291614329919796976150d9815f52600560205260405f2090565b976040860180516150e981611c00565b6150f281611c00565b615179575b50898886608089019561510a8751151590565b615165575b50505050505061512a606085015167ffffffffffffffff1690565b67ffffffffffffffff8116615140575051151590565b6144c590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b61516e95615d2e565b5f808988868361510f565b615187905161451881611c00565b5f6150f7565b90600a811015611c0a577fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000083549260401b169116179055565b9060c060049161520367ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116152b357505050565b5f5260205f20906020601f840160051c830193106152eb575b601f0160051c01905b8181106152e0575050565b5f81556001016152d5565b90915081906152cc565b919091825167ffffffffffffffff81116126ca5761531d8161531784546127cb565b846152a6565b6020601f82116001146153585781906131eb9394955f9261534d575b50508160011b915f199060031b1c19161790565b015190505f80615339565b601f1982169061536b845f5260205f2090565b915f5b8181106153a55750958360019596971061538d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615383565b9192602060018192868b01518155019401920161536e565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c0a5760c0600d916153fd61273a958561518d565b604081015160018501556154186060820151600286016151d0565b6154296080820151600786016151d0565b61543a60a0820151600c86016152f5565b015191016152f5565b6154596060919493945f525f60205260405f2090565b936154676080850151151590565b61563a575b01916154bf60a06154886020865101516001600160a01b031690565b9280515f81136155fc575b506020810180515f81136155b4575b5081515f8112615573575b50515f8112615528575b500151151590565b8061551a575b6154d6575b5050505061273a61467e565b61550f9261550260a0926154f6604060139601516001600160a01b031690565b90848451015191614d9d565b5101519201918254613352565b90555f8080806154ca565b5060a08351015115156154c5565b6143fd61553491614261565b61554f8561239b61071460408a01516001600160a01b031690565b61555a828254612c10565b905561556b60138901918254613352565b90555f6154b7565b6143fd61557f91614261565b61559d818761559860208b01516001600160a01b031690565b614d9d565b6155ac60138a01918254613352565b90555f6154ad565b6155bd90615ad0565b6155d88661239b61071460408b01516001600160a01b031690565b6155e3828254613352565b90556155f460138a01918254612c10565b90555f6154a2565b61560590615ad0565b615623818661561e60208a01516001600160a01b031690565b614527565b61563260138901918254612c10565b90555f615493565b61564781600587016153bd565b61546c565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156157e8575b806d04ee2d6d415b85acef8100000000600a9210156157cc575b662386f26fc100008110156157b7575b6305f5e1008110156157a5575b612710811015615795575b6064811015615786575b101561577b575b61571260216156da60018801615ddf565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561572257615712906156df565b50506001600160a01b036157478461573b858498615d73565b60208151910120615dc9565b9116931683146157735761576591816020611aad9351910120615dc9565b1461576e575f90565b600190565b505050600190565b6001909401936156c9565b600290606490049601956156c2565b60049061271090049601956156b8565b6008906305f5e10090049601956156ad565b601090662386f26fc1000090049601956156a0565b6020906d04ee2d6d415b85acef81000000009004960195615690565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615676565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00541461585f5760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff815116906020810151600a811015611c0a576159308260406159919401516158cf60806060840151930151946040519760208901526040880190611c19565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261084d61024082612707565b93909291935f52600260205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015192600a841015611c0a57615a5660c0615aa093615a0e615acc9760039a61518d565b60408101516007890155615a29606082015160088a016151d0565b615a3a6080820151600d8a016151d0565b615a4b60a082015160128a016152f5565b0151601387016152f5565b60018501907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b60028301906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0155565b5f8112615ada5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b818110615b3457505061273a92500383612707565b8454835260019485019487945060209093019201615b1f565b67ffffffffffffffff6004820154164210159081615b69575090565b600180925060ff91015416615b7d81611c00565b1490565b6001600160a01b039061410d615ba7615ba26020989599969799369061301c565b615887565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b6001810190825f528160205260405f2054155f14615c46578054680100000000000000008110156126ca57615c33615c1d8260018794018555846131bd565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c74575f190190615c6382826131bd565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d26575f19840184811161059a5783545f1981019490851161059a575f958583615ce397615cd69503615ce9575b505050615c4d565b905f5260205260405f2090565b55600190565b615d0f615d0991615d00610698615d1d95886131bd565b928391876131bd565b906131d2565b85905f5260205260405f2090565b555f8080615cce565b505050505f90565b93909291935f52600560205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b61273a90615dbb615db594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190613ff9565b90613ff9565b03601f198101845283612707565b61084d91615dd691615e06565b90929192615e40565b90615de982612faf565b615df66040519182612707565b828152601f196133488294612faf565b8151919060418303615e3657615e2f9250602082015190606060408401519301515f1a90615f07565b9192909190565b50505f9160029190565b615e4981611c00565b80615e52575050565b615e5b81611c00565b60018103615e8b577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e9481611c00565b60028103615ec857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ed4600392611c00565b14615edc5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f7e579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610488575f516001600160a01b03811615615f7457905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a1d82448e7e3f611b69660be3f5dc6e070ca23bb9e11aefc6fc6b7622d1cdc4e64736f6c634300081e0033000000000000000000000000735eb1026afba78b602da39c6b59eaba95753686", - "nonce": "0x2c", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", "chainId": "0xaa36a7" }, "additionalContracts": [], @@ -95,94 +44,46 @@ ], "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", + "cumulativeGasUsed": "0x16d9798", "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", + "transactionHash": "0x581be27b4d4b12595edd08dd50e3918e120bc78d2608cc359f85584d7896c576", + "transactionIndex": "0xbf", + "blockHash": "0x31d115e626acd302eb008c312c8ba101352b8c18a849e1b34f88d0d3be6e18c0", + "blockNumber": "0xa687c3", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x4241e512", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", "to": null, - "contractAddress": "0x735eb1026afba78b602da39c6b59eaba95753686" + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" }, { + "type": "0x2", "status": "0x1", - "cumulativeGasUsed": "0x11229e3", + "cumulativeGasUsed": "0x1c2db2b", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "type": "0x2", - "transactionHash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", - "transactionIndex": "0x6a", - "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", - "blockNumber": "0x9ebc3c", - "gasUsed": "0x51d590", - "effectiveGasPrice": "0x135b23", - "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "transactionHash": "0xa15ebf0cadb7273349070952636edfb166053f09f6e52fe57e73bfa02e1ce54a", + "transactionIndex": "0xc5", + "blockHash": "0x31d115e626acd302eb008c312c8ba101352b8c18a849e1b34f88d0d3be6e18c0", + "blockNumber": "0xa687c3", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x4241e512", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", "to": null, - "contractAddress": "0xb7be0e2007ddf320d680942cb9e008f986e74f83" + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" } ], "libraries": [ - "src/ChannelEngine.sol:ChannelEngine:0x78D150fdA6fa6739C18014B347c7c7C45C58e148", - "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x728904E52308213bA61C90EF49F34c18Fbda9E11", - "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x893F2D45fDFFe2D4297a5C1D5732EDce4849eE82" + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" ], "pending": [], "returns": {}, - "timestamp": 1772892720931, + "timestamp": 1779648336659, "chain": 11155111, - "commit": "fd394085" + "commit": "b88d511c" } \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/137/run-1779451180921.json b/contracts/broadcast/DeployChannelHub.s.sol/137/run-1779451180921.json new file mode 100644 index 000000000..bee685ebb --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/137/run-1779451180921.json @@ -0,0 +1,281 @@ +{ + "transactions": [ + { + "hash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "transactionType": "CREATE", + "contractName": "ECDSAValidator", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x10da09c", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000002272ca51c900d0000000000000000000000000000000000000000000000000068155a43676e000000000000000000000000000000000000000000000013fdb22e4c7e88629e501e00000000000000000000000000000000000000000000000065ee2d9e4addf30000000000000000000000000000000000000000000013fdb23073ab2d7f2e5d1e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "transactionIndex": "0x4d", + "logIndex": "0x23f", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "transactionIndex": "0x4d", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x118c12c", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000014e6cb8debbf00000000000000000000000000000000000000000000000000061a47dd4909cb01100000000000000000000000000000000000000000013fdb23073ab2d7f2e5d1e0000000000000000000000000000000000000000000000006056111bb1e0c01100000000000000000000000000000000000000000013fdb231c217e65dea4d1e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "transactionIndex": "0x4e", + "logIndex": "0x240", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "transactionIndex": "0x4e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1238b26", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000001444268267cd6000000000000000000000000000000000000000000000000005dbc0bd204fc6ac100000000000000000000000000000000000000000013fdb231c217e65dea4d1e0000000000000000000000000000000000000000000000005c77c969de7f94c100000000000000000000000000000000000000000013fdb233065a4e8467231e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "transactionIndex": "0x4f", + "logIndex": "0x241", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "transactionIndex": "0x4f", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x129ed67", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000000bfdd074e11470000000000000000000000000000000000000000000000000059f202bea1cc3a4f00000000000000000000000000000000000000000013fdb233065a4e8467231e000000000000000000000000000000000000000000000000593225b753baf34f00000000000000000000000000000000000000000013fdb233c63755d2786a1e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "transactionIndex": "0x50", + "logIndex": "0x242", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "transactionIndex": "0x50", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x17a4686", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000096ed6fc6a1e590000000000000000000000000000000000000000000000000057b40b1dbde6a22200000000000000000000000000000000000000000013fdb233c63755d2786a1e0000000000000000000000000000000000000000000000004e45342153c8492200000000000000000000000000000000000000000013fdb23d350e523c96c31e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "transactionIndex": "0x51", + "logIndex": "0x243", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "transactionIndex": "0x51", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0x50591f", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779451180921, + "chain": 137, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/137/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/137/run-latest.json new file mode 100644 index 000000000..cbfe4e87e --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/137/run-latest.json @@ -0,0 +1,281 @@ +{ + "transactions": [ + { + "hash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "transactionType": "CREATE", + "contractName": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x89" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x10da09c", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000002272ca51c900d0000000000000000000000000000000000000000000000000068155a43676e000000000000000000000000000000000000000000000013fdb22e4c7e88629e501e00000000000000000000000000000000000000000000000065ee2d9e4addf30000000000000000000000000000000000000000000013fdb23073ab2d7f2e5d1e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "transactionIndex": "0x4d", + "logIndex": "0x23f", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "transactionIndex": "0x4d", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x118c12c", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000014e6cb8debbf00000000000000000000000000000000000000000000000000061a47dd4909cb01100000000000000000000000000000000000000000013fdb23073ab2d7f2e5d1e0000000000000000000000000000000000000000000000006056111bb1e0c01100000000000000000000000000000000000000000013fdb231c217e65dea4d1e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "transactionIndex": "0x4e", + "logIndex": "0x240", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "transactionIndex": "0x4e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1238b26", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000001444268267cd6000000000000000000000000000000000000000000000000005dbc0bd204fc6ac100000000000000000000000000000000000000000013fdb231c217e65dea4d1e0000000000000000000000000000000000000000000000005c77c969de7f94c100000000000000000000000000000000000000000013fdb233065a4e8467231e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "transactionIndex": "0x4f", + "logIndex": "0x241", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "transactionIndex": "0x4f", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x129ed67", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000000bfdd074e11470000000000000000000000000000000000000000000000000059f202bea1cc3a4f00000000000000000000000000000000000000000013fdb233065a4e8467231e000000000000000000000000000000000000000000000000593225b753baf34f00000000000000000000000000000000000000000013fdb233c63755d2786a1e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "transactionIndex": "0x50", + "logIndex": "0x242", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "transactionIndex": "0x50", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x17a4686", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000e6521da9b54740e5fe9e002eb7bbf86d5698e581", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000096ed6fc6a1e590000000000000000000000000000000000000000000000000057b40b1dbde6a22200000000000000000000000000000000000000000013fdb233c63755d2786a1e0000000000000000000000000000000000000000000000004e45342153c8492200000000000000000000000000000000000000000013fdb23d350e523c96c31e", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "blockTimestamp": "0x6a10452e", + "transactionHash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "transactionIndex": "0x51", + "logIndex": "0x243", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000200100000000000000000000000000800000000000000000000000000000000000000000000100000", + "transactionHash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "transactionIndex": "0x51", + "blockHash": "0x4f8238786f2db8758c49d65770f0cd669bf1db247b4b5756492b3157e774cfe1", + "blockNumber": "0x53389d3", + "gasUsed": "0x50591f", + "effectiveGasPrice": "0x59e8f022ed", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779451180921, + "chain": 137, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/14/run-1779452671671.json b/contracts/broadcast/DeployChannelHub.s.sol/14/run-1779452671671.json new file mode 100644 index 000000000..57c30e91b --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/14/run-1779452671671.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x803f6d76e2293f03d2c2cd6f547df507828b4a71ea63d8706c930ff8840b32eb", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xdd4ea848360946f4a0699a2bbc85fc1886164f6de8d213cfecc818934e653772", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x70e5c4cc39b9397e2c6181f43b7b15e3d8bfec7ccb3b1d86e12ba3cabe082b33", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3ae69da40a8649d359ab7d4adafbdfc9d86cd32be2a534ac58f1bd9675a96363", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xdcf31cd7c01c6a548c7a0a02171ab7694386b1a489c206133b63f24d87420a75", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x26604c", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x803f6d76e2293f03d2c2cd6f547df507828b4a71ea63d8706c930ff8840b32eb", + "transactionIndex": "0x4", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3180dc", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xdd4ea848360946f4a0699a2bbc85fc1886164f6de8d213cfecc818934e653772", + "transactionIndex": "0x5", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3c4ad6", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x70e5c4cc39b9397e2c6181f43b7b15e3d8bfec7ccb3b1d86e12ba3cabe082b33", + "transactionIndex": "0x6", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x42ad17", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x3ae69da40a8649d359ab7d4adafbdfc9d86cd32be2a534ac58f1bd9675a96363", + "transactionIndex": "0x7", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x9302ee", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xdcf31cd7c01c6a548c7a0a02171ab7694386b1a489c206133b63f24d87420a75", + "transactionIndex": "0x8", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779452671671, + "chain": 14, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/14/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/14/run-latest.json new file mode 100644 index 000000000..a2403bae7 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/14/run-latest.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x803f6d76e2293f03d2c2cd6f547df507828b4a71ea63d8706c930ff8840b32eb", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xdd4ea848360946f4a0699a2bbc85fc1886164f6de8d213cfecc818934e653772", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x70e5c4cc39b9397e2c6181f43b7b15e3d8bfec7ccb3b1d86e12ba3cabe082b33", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3ae69da40a8649d359ab7d4adafbdfc9d86cd32be2a534ac58f1bd9675a96363", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xdcf31cd7c01c6a548c7a0a02171ab7694386b1a489c206133b63f24d87420a75", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0xe" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x26604c", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x803f6d76e2293f03d2c2cd6f547df507828b4a71ea63d8706c930ff8840b32eb", + "transactionIndex": "0x4", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3180dc", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xdd4ea848360946f4a0699a2bbc85fc1886164f6de8d213cfecc818934e653772", + "transactionIndex": "0x5", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3c4ad6", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x70e5c4cc39b9397e2c6181f43b7b15e3d8bfec7ccb3b1d86e12ba3cabe082b33", + "transactionIndex": "0x6", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x42ad17", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x3ae69da40a8649d359ab7d4adafbdfc9d86cd32be2a534ac58f1bd9675a96363", + "transactionIndex": "0x7", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x9302ee", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xdcf31cd7c01c6a548c7a0a02171ab7694386b1a489c206133b63f24d87420a75", + "transactionIndex": "0x8", + "blockHash": "0x123ffafccd20afc193c8b2c0d6e2fdcf3c6b9c9f160ffb697b188645ed5e8a96", + "blockNumber": "0x3a8308a", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x1e0cc06b", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779452671671, + "chain": 14, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1440000/run-1779452459580.json b/contracts/broadcast/DeployChannelHub.s.sol/1440000/run-1779452459580.json new file mode 100644 index 000000000..8e6b160d6 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1440000/run-1779452459580.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x6c9e41141b79c21edd45d64678f31b6f457672b7b8206047f6e46545294a816f", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa22adafb38b1e88a5c00273904fc15eeeea3c96d3f796a193f30ad8169f02da5", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2577f78efe05b0f1eb6019a07828785182877992b904d5c2d80b278b5903d8d3", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x25d519a7a16430b5c2ef8df85a04aa37184aa45257bdcff4130f4ef2d09a7123", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5013e736d9939604d954d9a43569e120c08e2eff473a29d655a22ec637845cef", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1256cb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6c9e41141b79c21edd45d64678f31b6f457672b7b8206047f6e46545294a816f", + "transactionIndex": "0x0", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb2090", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa22adafb38b1e88a5c00273904fc15eeeea3c96d3f796a193f30ad8169f02da5", + "transactionIndex": "0x1", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xac9fa", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x2577f78efe05b0f1eb6019a07828785182877992b904d5c2d80b278b5903d8d3", + "transactionIndex": "0x2", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x25d519a7a16430b5c2ef8df85a04aa37184aa45257bdcff4130f4ef2d09a7123", + "transactionIndex": "0x3", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5055d7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x5013e736d9939604d954d9a43569e120c08e2eff473a29d655a22ec637845cef", + "transactionIndex": "0x4", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779452459580, + "chain": 1440000, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1440000/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/1440000/run-latest.json new file mode 100644 index 000000000..b05548a93 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1440000/run-latest.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x6c9e41141b79c21edd45d64678f31b6f457672b7b8206047f6e46545294a816f", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa22adafb38b1e88a5c00273904fc15eeeea3c96d3f796a193f30ad8169f02da5", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2577f78efe05b0f1eb6019a07828785182877992b904d5c2d80b278b5903d8d3", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x25d519a7a16430b5c2ef8df85a04aa37184aa45257bdcff4130f4ef2d09a7123", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5013e736d9939604d954d9a43569e120c08e2eff473a29d655a22ec637845cef", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x15f900" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1256cb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6c9e41141b79c21edd45d64678f31b6f457672b7b8206047f6e46545294a816f", + "transactionIndex": "0x0", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb2090", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa22adafb38b1e88a5c00273904fc15eeeea3c96d3f796a193f30ad8169f02da5", + "transactionIndex": "0x1", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xac9fa", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x2577f78efe05b0f1eb6019a07828785182877992b904d5c2d80b278b5903d8d3", + "transactionIndex": "0x2", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x25d519a7a16430b5c2ef8df85a04aa37184aa45257bdcff4130f4ef2d09a7123", + "transactionIndex": "0x3", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5055d7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x5013e736d9939604d954d9a43569e120c08e2eff473a29d655a22ec637845cef", + "transactionIndex": "0x4", + "blockHash": "0x73d9235dda6e353018e61f4b87a7f023a55c0861450e9c16bcfd652ad41fca88", + "blockNumber": "0x5b123e", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779452459580, + "chain": 1440000, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-1779364442629.json b/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-1779364442629.json new file mode 100644 index 000000000..87e6f05bf --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-1779364442629.json @@ -0,0 +1,115 @@ +{ + "transactions": [ + { + "hash": "0xdc22db9c5f7952782b70276b78a7fb5f51ba60edecac324f578ab2eae54ef578", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xb96a14ffbb8eeea30e92b20e03926112892748a5ea99e293dba75e850a372192", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa3f46d8e4cfdd400280eb8c17023c2355501c646269de13b1f2308629045ae3d", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x63950ae96d7eab4a5387be37fdc8fed2c0974d1033c139224baa78874497b846", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x4ae98bc4da7bf9f27956b9faf1f273090ef759da", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": null, + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xa6bdaf7a0076d269a9b0ab8a06fe2ab2fda5ea2b", + "function": null, + "arguments": [ + "0x4Ae98BC4DA7BF9F27956b9FAf1f273090eF759da", + "0xc76632D91D45Ec88304ab2a983451d9EDf908C0d" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e00330000000000000000000000004ae98bc4da7bf9f27956b9faf1f273090ef759da000000000000000000000000c76632d91d45ec88304ab2a983451d9edf908c0d", + "nonce": "0x4", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [ + "0xdc22db9c5f7952782b70276b78a7fb5f51ba60edecac324f578ab2eae54ef578", + "0xb96a14ffbb8eeea30e92b20e03926112892748a5ea99e293dba75e850a372192", + "0xa3f46d8e4cfdd400280eb8c17023c2355501c646269de13b1f2308629045ae3d", + "0x63950ae96d7eab4a5387be37fdc8fed2c0974d1033c139224baa78874497b846" + ], + "returns": {}, + "timestamp": 1779364442629, + "chain": 1449000, + "commit": "a5cb25e2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-1779647967923.json b/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-1779647967923.json new file mode 100644 index 000000000..3a9064683 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-1779647967923.json @@ -0,0 +1,89 @@ +{ + "transactions": [ + { + "hash": "0x1d4eed14867460fc5680093662a6525c6762d9f9c7d690fc26980a43fdaff9e8", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xaa23e4554906462d2056520d2e518a5605e1651dbd2c1aa7914adc9a140090b7", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1d4eed14867460fc5680093662a6525c6762d9f9c7d690fc26980a43fdaff9e8", + "transactionIndex": "0x0", + "blockHash": "0xd474fa6403f079d5a060da15270dda30948131680a84926c95674aadd233f23f", + "blockNumber": "0x6a212a", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5055d7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xaa23e4554906462d2056520d2e518a5605e1651dbd2c1aa7914adc9a140090b7", + "transactionIndex": "0x0", + "blockHash": "0xf65f06de13791205e70a0ab004952a597ba5606aa59f1d119a6e3a3a3899c4d2", + "blockNumber": "0x6a212b", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779647967923, + "chain": 1449000, + "commit": "b88d511c" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-latest.json new file mode 100644 index 000000000..6fc9bc317 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/1449000/run-latest.json @@ -0,0 +1,89 @@ +{ + "transactions": [ + { + "hash": "0x1d4eed14867460fc5680093662a6525c6762d9f9c7d690fc26980a43fdaff9e8", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xaa23e4554906462d2056520d2e518a5605e1651dbd2c1aa7914adc9a140090b7", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1d4eed14867460fc5680093662a6525c6762d9f9c7d690fc26980a43fdaff9e8", + "transactionIndex": "0x0", + "blockHash": "0xd474fa6403f079d5a060da15270dda30948131680a84926c95674aadd233f23f", + "blockNumber": "0x6a212a", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5055d7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xaa23e4554906462d2056520d2e518a5605e1651dbd2c1aa7914adc9a140090b7", + "transactionIndex": "0x0", + "blockHash": "0xf65f06de13791205e70a0ab004952a597ba5606aa59f1d119a6e3a3a3899c4d2", + "blockNumber": "0x6a212b", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779647967923, + "chain": 1449000, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/480/run-1779454192661.json b/contracts/broadcast/DeployChannelHub.s.sol/480/run-1779454192661.json new file mode 100644 index 000000000..640030fad --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/480/run-1779454192661.json @@ -0,0 +1,231 @@ +{ + "transactions": [ + { + "hash": "0xf596d5d74c2b19cacffb933b4edc5133cf81af5c369864e33f80e0959362915a", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x16796168009fc482674462e80b9a8985b6ebcdcc53ce8a9d9290ac04dd4eb07e", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x03d5005e4380e60dde490cb28652ce8cb5894edd7c1bdd5e84d21fa6ed4db90e", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x667c15364a70941a6914df17d30e509f9e1d347b1f35bffd353d89fc8b20ca02", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa681431f362b46fba7e2416205ad0116316a13c88eaba206fe3d2f32aa630240", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x6efaed", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xf596d5d74c2b19cacffb933b4edc5133cf81af5c369864e33f80e0959362915a", + "transactionIndex": "0x1b", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0x10fc70", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0xadff", + "l1Fee": "0x117870724c", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7a1b7d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x16796168009fc482674462e80b9a8985b6ebcdcc53ce8a9d9290ac04dd4eb07e", + "transactionIndex": "0x1c", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0xbb990", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x7818", + "l1Fee": "0xc0eef4807", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x8e7376", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x667c15364a70941a6914df17d30e509f9e1d347b1f35bffd353d89fc8b20ca02", + "transactionIndex": "0x1e", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0xb4460", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x7363", + "l1Fee": "0xb95fd15a8", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x94d5b7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x03d5005e4380e60dde490cb28652ce8cb5894edd7c1bdd5e84d21fa6ed4db90e", + "transactionIndex": "0x1f", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0x749a0", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x4aa9", + "l1Fee": "0x77f221206", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xe52b8e", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa681431f362b46fba7e2416205ad0116316a13c88eaba206fe3d2f32aa630240", + "transactionIndex": "0x20", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0x5499d0", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x3625b", + "l1Fee": "0x56fcfbb00e", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779454192661, + "chain": 480, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/480/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/480/run-latest.json new file mode 100644 index 000000000..035b4a64b --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/480/run-latest.json @@ -0,0 +1,231 @@ +{ + "transactions": [ + { + "hash": "0xf596d5d74c2b19cacffb933b4edc5133cf81af5c369864e33f80e0959362915a", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x16796168009fc482674462e80b9a8985b6ebcdcc53ce8a9d9290ac04dd4eb07e", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x03d5005e4380e60dde490cb28652ce8cb5894edd7c1bdd5e84d21fa6ed4db90e", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x667c15364a70941a6914df17d30e509f9e1d347b1f35bffd353d89fc8b20ca02", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa681431f362b46fba7e2416205ad0116316a13c88eaba206fe3d2f32aa630240", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x1e0" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x6efaed", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xf596d5d74c2b19cacffb933b4edc5133cf81af5c369864e33f80e0959362915a", + "transactionIndex": "0x1b", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0x10fc70", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0xadff", + "l1Fee": "0x117870724c", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7a1b7d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x16796168009fc482674462e80b9a8985b6ebcdcc53ce8a9d9290ac04dd4eb07e", + "transactionIndex": "0x1c", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0xbb990", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x7818", + "l1Fee": "0xc0eef4807", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x8e7376", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x667c15364a70941a6914df17d30e509f9e1d347b1f35bffd353d89fc8b20ca02", + "transactionIndex": "0x1e", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0xb4460", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x7363", + "l1Fee": "0xb95fd15a8", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x94d5b7", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x03d5005e4380e60dde490cb28652ce8cb5894edd7c1bdd5e84d21fa6ed4db90e", + "transactionIndex": "0x1f", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0x749a0", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x4aa9", + "l1Fee": "0x77f221206", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xe52b8e", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa681431f362b46fba7e2416205ad0116316a13c88eaba206fe3d2f32aa630240", + "transactionIndex": "0x20", + "blockHash": "0xad153ba0f0a0c79de896a38e72a2e9e9438acca7fe04aaf045afb9dbe6298a44", + "blockNumber": "0x1caab0c", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x927c0", + "blobGasUsed": "0x5499d0", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "l1GasPrice": "0x86ab7b0", + "l1GasUsed": "0x3625b", + "l1Fee": "0x56fcfbb00e", + "l1BaseFeeScalar": "0x21f9", + "l1BlobBaseFee": "0x7af6e7", + "l1BlobBaseFeeScalar": "0xdd3ef", + "daFootprintGasScalar": "0x190" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779454192661, + "chain": 480, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/56/run-1779451411055.json b/contracts/broadcast/DeployChannelHub.s.sol/56/run-1779451411055.json new file mode 100644 index 000000000..2f1a78458 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/56/run-1779451411055.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x482709d9867b6ccb857df938deff48bb201a2e759b9c2323fbdaea2973e49c2f", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x985e1048fe3359fbdbb78845ad836051538a2efc8fe16516debfc2cb7fbd88cb", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xabacfdc37a4fdc8b2d4dea6e64e810eae73518ac51abe4eaa55c0ef2f93b9ca6", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5b0f4c1e0255284701b924d2782bf997342da5fdfd90bd000f9ff92306712a8e", + "transactionType": "CREATE", + "contractName": "ECDSAValidator", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x639841bde9847da0980121aa4a94ce88f830b446c9e911bc3f48fbd232ea7aa1", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x297f5a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x482709d9867b6ccb857df938deff48bb201a2e759b9c2323fbdaea2973e49c2f", + "transactionIndex": "0x4", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x349fea", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x5b0f4c1e0255284701b924d2782bf997342da5fdfd90bd000f9ff92306712a8e", + "transactionIndex": "0x5", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x10025c3", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x985e1048fe3359fbdbb78845ad836051538a2efc8fe16516debfc2cb7fbd88cb", + "transactionIndex": "0x58", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1068804", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xabacfdc37a4fdc8b2d4dea6e64e810eae73518ac51abe4eaa55c0ef2f93b9ca6", + "transactionIndex": "0x59", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x11ced2d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x639841bde9847da0980121aa4a94ce88f830b446c9e911bc3f48fbd232ea7aa1", + "transactionIndex": "0x4a", + "blockHash": "0x0f7d5fe4c9afd46887b3a3de9e3ef1e4aa11c9e7d320667c5df26f38a0cf5846", + "blockNumber": "0x5f25b22", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779451411055, + "chain": 56, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/56/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/56/run-latest.json new file mode 100644 index 000000000..98a01753f --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/56/run-latest.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x482709d9867b6ccb857df938deff48bb201a2e759b9c2323fbdaea2973e49c2f", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x985e1048fe3359fbdbb78845ad836051538a2efc8fe16516debfc2cb7fbd88cb", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xabacfdc37a4fdc8b2d4dea6e64e810eae73518ac51abe4eaa55c0ef2f93b9ca6", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5b0f4c1e0255284701b924d2782bf997342da5fdfd90bd000f9ff92306712a8e", + "transactionType": "CREATE", + "contractName": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x639841bde9847da0980121aa4a94ce88f830b446c9e911bc3f48fbd232ea7aa1", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x38" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x297f5a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x482709d9867b6ccb857df938deff48bb201a2e759b9c2323fbdaea2973e49c2f", + "transactionIndex": "0x4", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x349fea", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x5b0f4c1e0255284701b924d2782bf997342da5fdfd90bd000f9ff92306712a8e", + "transactionIndex": "0x5", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x10025c3", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x985e1048fe3359fbdbb78845ad836051538a2efc8fe16516debfc2cb7fbd88cb", + "transactionIndex": "0x58", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1068804", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xabacfdc37a4fdc8b2d4dea6e64e810eae73518ac51abe4eaa55c0ef2f93b9ca6", + "transactionIndex": "0x59", + "blockHash": "0x41aa5cf9829c0e67ab617d59f4c193e1d8989d95bff1a44162358e5eb8da8d1a", + "blockNumber": "0x5f25b21", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x11ced2d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x639841bde9847da0980121aa4a94ce88f830b446c9e911bc3f48fbd232ea7aa1", + "transactionIndex": "0x4a", + "blockHash": "0x0f7d5fe4c9afd46887b3a3de9e3ef1e4aa11c9e7d320667c5df26f38a0cf5846", + "blockNumber": "0x5f25b22", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2faf080", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779451411055, + "chain": 56, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/59141/run-1779363212273.json b/contracts/broadcast/DeployChannelHub.s.sol/59141/run-1779363212273.json new file mode 100644 index 000000000..39f4e7a73 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/59141/run-1779363212273.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x2ac9ccb2d6a7a9f0f6aaaa515588a8dd3ffbd87f6965dac4280a0bd7826c5f24", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x8a149aaf4be2aa8f0d2ebae69ab29ede62e20494891a5a287e6fa7c86397f079", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x8590922c6571c3e212736e1c88cd82d2a38a13c4026fa71ecd331ffd288dd295", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x80fced0533eca3d25b97fb2645004e4a740898bb31f44a921a50dbeb41410d83", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x4ae98bc4da7bf9f27956b9faf1f273090ef759da", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x107aa27591309d37b5e38df4bbc4af4f824de4c2681c9c4a4245ce765bbf7537", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xa6bdaf7a0076d269a9b0ab8a06fe2ab2fda5ea2b", + "function": null, + "arguments": [ + "0x4Ae98BC4DA7BF9F27956b9FAf1f273090eF759da", + "0xc76632D91D45Ec88304ab2a983451d9EDf908C0d" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e00330000000000000000000000004ae98bc4da7bf9f27956b9faf1f273090ef759da000000000000000000000000c76632d91d45ec88304ab2a983451d9edf908c0d", + "nonce": "0x4", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1256cb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x2ac9ccb2d6a7a9f0f6aaaa515588a8dd3ffbd87f6965dac4280a0bd7826c5f24", + "transactionIndex": "0x0", + "blockHash": "0x1a51a806e43a05361cf9b45e27fea0c08c178017ca1e0049f1d1f92da0bcbcf6", + "blockNumber": "0x1c4732f", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2333f68", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1d775b", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x8590922c6571c3e212736e1c88cd82d2a38a13c4026fa71ecd331ffd288dd295", + "transactionIndex": "0x1", + "blockHash": "0x1a51a806e43a05361cf9b45e27fea0c08c178017ca1e0049f1d1f92da0bcbcf6", + "blockNumber": "0x1c4732f", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2333f68", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x284155", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x8a149aaf4be2aa8f0d2ebae69ab29ede62e20494891a5a287e6fa7c86397f079", + "transactionIndex": "0x2", + "blockHash": "0x1a51a806e43a05361cf9b45e27fea0c08c178017ca1e0049f1d1f92da0bcbcf6", + "blockNumber": "0x1c4732f", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2333f68", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x2ea396", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x80fced0533eca3d25b97fb2645004e4a740898bb31f44a921a50dbeb41410d83", + "transactionIndex": "0x3", + "blockHash": "0x1a51a806e43a05361cf9b45e27fea0c08c178017ca1e0049f1d1f92da0bcbcf6", + "blockNumber": "0x1c4732f", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2333f68", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "contractAddress": "0x4ae98bc4da7bf9f27956b9faf1f273090ef759da" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7ef96d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x107aa27591309d37b5e38df4bbc4af4f824de4c2681c9c4a4245ce765bbf7537", + "transactionIndex": "0x4", + "blockHash": "0x1a51a806e43a05361cf9b45e27fea0c08c178017ca1e0049f1d1f92da0bcbcf6", + "blockNumber": "0x1c4732f", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2333f68", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "contractAddress": "0xa6bdaf7a0076d269a9b0ab8a06fe2ab2fda5ea2b" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779363212273, + "chain": 59141, + "commit": "76cc8d2c" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/59141/run-1779646931100.json b/contracts/broadcast/DeployChannelHub.s.sol/59141/run-1779646931100.json new file mode 100644 index 000000000..c44eb058f --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/59141/run-1779646931100.json @@ -0,0 +1,89 @@ +{ + "transactions": [ + { + "hash": "0x247f3062d94171b11d634431a1168f6d0add702f08cf5e52acb8ed0642ea7f41", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6392cece24358fce8adc14e780f6d34a32ac6c3d699dbf8260edc71432d77327", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x247f3062d94171b11d634431a1168f6d0add702f08cf5e52acb8ed0642ea7f41", + "transactionIndex": "0x0", + "blockHash": "0x9b42d90c4cc19a6a0f1c575ed2785929cef8167ef271a4585f5c473b334bcdd6", + "blockNumber": "0x1c59518", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2331c40", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x56b818", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6392cece24358fce8adc14e780f6d34a32ac6c3d699dbf8260edc71432d77327", + "transactionIndex": "0x1", + "blockHash": "0x9b42d90c4cc19a6a0f1c575ed2785929cef8167ef271a4585f5c473b334bcdd6", + "blockNumber": "0x1c59518", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2331c40", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779646931100, + "chain": 59141, + "commit": "b88d511c" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/59141/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/59141/run-latest.json new file mode 100644 index 000000000..76123b86d --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/59141/run-latest.json @@ -0,0 +1,89 @@ +{ + "transactions": [ + { + "hash": "0x247f3062d94171b11d634431a1168f6d0add702f08cf5e52acb8ed0642ea7f41", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6392cece24358fce8adc14e780f6d34a32ac6c3d699dbf8260edc71432d77327", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x247f3062d94171b11d634431a1168f6d0add702f08cf5e52acb8ed0642ea7f41", + "transactionIndex": "0x0", + "blockHash": "0x9b42d90c4cc19a6a0f1c575ed2785929cef8167ef271a4585f5c473b334bcdd6", + "blockNumber": "0x1c59518", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2331c40", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x56b818", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6392cece24358fce8adc14e780f6d34a32ac6c3d699dbf8260edc71432d77327", + "transactionIndex": "0x1", + "blockHash": "0x9b42d90c4cc19a6a0f1c575ed2785929cef8167ef271a4585f5c473b334bcdd6", + "blockNumber": "0x1c59518", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2331c40", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779646931100, + "chain": 59141, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/59144/run-1779451960724.json b/contracts/broadcast/DeployChannelHub.s.sol/59144/run-1779451960724.json new file mode 100644 index 000000000..e22db6574 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/59144/run-1779451960724.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x46748d521e948da676b5a4a49580316375b512fe356e03740d87f1321db3ccf8", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x8d06af1943f08a1f9c444fca28eaeb0bcdccad331a5049c69f0ea216b10fb2e1", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xcc6b8bb892df220c68efa59cb79544edccd437f1d55ae84900f6e0000558c13d", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd6285f3667e6ef5c98f210583e3fee6f67ed755f8a70cd2de58c4732a7421a94", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa91c6de70e8b9e6f9b943d4a2a1c9895ae816d1aa1dbc98a3f7e48d35c5558f0", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x141d03", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x46748d521e948da676b5a4a49580316375b512fe356e03740d87f1321db3ccf8", + "transactionIndex": "0x1", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1f3d93", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x8d06af1943f08a1f9c444fca28eaeb0bcdccad331a5049c69f0ea216b10fb2e1", + "transactionIndex": "0x2", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x2a078d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xcc6b8bb892df220c68efa59cb79544edccd437f1d55ae84900f6e0000558c13d", + "transactionIndex": "0x3", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3069ce", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xd6285f3667e6ef5c98f210583e3fee6f67ed755f8a70cd2de58c4732a7421a94", + "transactionIndex": "0x4", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x80bfa5", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa91c6de70e8b9e6f9b943d4a2a1c9895ae816d1aa1dbc98a3f7e48d35c5558f0", + "transactionIndex": "0x5", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779451960724, + "chain": 59144, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/59144/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/59144/run-latest.json new file mode 100644 index 000000000..d6a22e2ae --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/59144/run-latest.json @@ -0,0 +1,191 @@ +{ + "transactions": [ + { + "hash": "0x46748d521e948da676b5a4a49580316375b512fe356e03740d87f1321db3ccf8", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x8d06af1943f08a1f9c444fca28eaeb0bcdccad331a5049c69f0ea216b10fb2e1", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xcc6b8bb892df220c68efa59cb79544edccd437f1d55ae84900f6e0000558c13d", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd6285f3667e6ef5c98f210583e3fee6f67ed755f8a70cd2de58c4732a7421a94", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa91c6de70e8b9e6f9b943d4a2a1c9895ae816d1aa1dbc98a3f7e48d35c5558f0", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0xe708" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x141d03", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x46748d521e948da676b5a4a49580316375b512fe356e03740d87f1321db3ccf8", + "transactionIndex": "0x1", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1f3d93", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x8d06af1943f08a1f9c444fca28eaeb0bcdccad331a5049c69f0ea216b10fb2e1", + "transactionIndex": "0x2", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x2a078d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xcc6b8bb892df220c68efa59cb79544edccd437f1d55ae84900f6e0000558c13d", + "transactionIndex": "0x3", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3069ce", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xd6285f3667e6ef5c98f210583e3fee6f67ed755f8a70cd2de58c4732a7421a94", + "transactionIndex": "0x4", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x80bfa5", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa91c6de70e8b9e6f9b943d4a2a1c9895ae816d1aa1dbc98a3f7e48d35c5558f0", + "transactionIndex": "0x5", + "blockHash": "0x49d23764fa5ccf37a71885ccc3abbe9a52235d95bfaa9083bd87c21f02c30683", + "blockNumber": "0x1d512a1", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x2a0922c", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779451960724, + "chain": 59144, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1779355867097.json b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1779355867097.json new file mode 100644 index 000000000..b15b7c244 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1779355867097.json @@ -0,0 +1,281 @@ +{ + "transactions": [ + { + "hash": "0xcfcabc6f5e685e11cd0ee8916a495f171b5ce6dd1a803d3baf123b70d8d23f12", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x81b8e83810f5c33fb9224e184defbae5197f688eb95124bff530f020af1f4c98", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x01f9e653982796b7b022ed0d475745c76904848e9de7dada1c2aa232974697a5", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x04943fa940d4f8e9d703edb8738ee2ff57ef0d8f76235809238e9a5cf0ba9a5a", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x4ae98bc4da7bf9f27956b9faf1f273090ef759da", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x7bd08b64777951b41fae7df49bb5ace73511ce8a22d1b9a716ac6e0e3cccb2e5", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xa6bdaf7a0076d269a9b0ab8a06fe2ab2fda5ea2b", + "function": null, + "arguments": [ + "0x4Ae98BC4DA7BF9F27956b9FAf1f273090eF759da", + "0xc76632D91D45Ec88304ab2a983451d9EDf908C0d" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e00330000000000000000000000004ae98bc4da7bf9f27956b9faf1f273090ef759da000000000000000000000000c76632d91d45ec88304ab2a983451d9edf908c0d", + "nonce": "0x4", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1256cb", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000014d0d2670fa04000000000000000000000000000000000000000000000000001bc16d674ec800000000000000000000000000000000000000000000000047bc0d4f995a5a0dea4a0000000000000000000000000000000000000000000000001a746040ddcdfc000000000000000000000000000000000000000000000047bc0e9ca680cb07ee4a", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "blockTimestamp": "0x6a0ed0dc", + "transactionHash": "0xcfcabc6f5e685e11cd0ee8916a495f171b5ce6dd1a803d3baf123b70d8d23f12", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0xcfcabc6f5e685e11cd0ee8916a495f171b5ce6dd1a803d3baf123b70d8d23f12", + "transactionIndex": "0x0", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x1229298c3f", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1d775b", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000000ca14255cdec0000000000000000000000000000000000000000000000000001a746040d94aa00b0000000000000000000000000000000000000000000047bc0e9ca680cb07ee4a00000000000000000000000000000000000000000000000019aa4c1b7c6be00b0000000000000000000000000000000000000000000047bc0f66baa627e6ae4a", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "blockTimestamp": "0x6a0ed0dc", + "transactionHash": "0x81b8e83810f5c33fb9224e184defbae5197f688eb95124bff530f020af1f4c98", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0x81b8e83810f5c33fb9224e184defbae5197f688eb95124bff530f020af1f4c98", + "transactionIndex": "0x1", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x1229298c3f", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x284155", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000000c3efad497eb80000000000000000000000000000000000000000000000000019aa4c1b79aedc9b0000000000000000000000000000000000000000000047bc0f66baa627e6ae4a00000000000000000000000000000000000000000000000018e65c6e3030249b0000000000000000000000000000000000000000000047bc102aaa537165664a", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "blockTimestamp": "0x6a0ed0dc", + "transactionHash": "0x04943fa940d4f8e9d703edb8738ee2ff57ef0d8f76235809238e9a5cf0ba9a5a", + "transactionIndex": "0x2", + "logIndex": "0x2", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0x04943fa940d4f8e9d703edb8738ee2ff57ef0d8f76235809238e9a5cf0ba9a5a", + "transactionIndex": "0x2", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x1229298c3f", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x2ea396", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x0000000000000000000000000000000000000000000000000073ef57a3248c0000000000000000000000000000000000000000000000000018e65c6e2d8870150000000000000000000000000000000000000000000047bc102aaa537165664a00000000000000000000000000000000000000000000000018726d168a63e4150000000000000000000000000000000000000000000047bc109e99ab1489f24a", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "blockTimestamp": "0x6a0ed0dc", + "transactionHash": "0x01f9e653982796b7b022ed0d475745c76904848e9de7dada1c2aa232974697a5", + "transactionIndex": "0x3", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0x01f9e653982796b7b022ed0d475745c76904848e9de7dada1c2aa232974697a5", + "transactionIndex": "0x3", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x1229298c3f", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "contractAddress": "0x4ae98bc4da7bf9f27956b9faf1f273090ef759da" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7efcb5", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000005b32f7e2db3f40000000000000000000000000000000000000000000000000018726d1688d1b6160000000000000000000000000000000000000000000047bc109e99ab1489f24a00000000000000000000000000000000000000000000000012bf3d985b1dc2160000000000000000000000000000000000000000000047bc1651c929423de64a", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "blockTimestamp": "0x6a0ed0dc", + "transactionHash": "0x7bd08b64777951b41fae7df49bb5ace73511ce8a22d1b9a716ac6e0e3cccb2e5", + "transactionIndex": "0x4", + "logIndex": "0x4", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000400000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0x7bd08b64777951b41fae7df49bb5ace73511ce8a22d1b9a716ac6e0e3cccb2e5", + "transactionIndex": "0x4", + "blockHash": "0xa56d5864187affc6e7528c71ff8676430d6cda298a5a4ff385bf0cc51f287134", + "blockNumber": "0x24f47d5", + "gasUsed": "0x50591f", + "effectiveGasPrice": "0x1229298c3f", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "contractAddress": "0xa6bdaf7a0076d269a9b0ab8a06fe2ab2fda5ea2b" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779355867097, + "chain": 80002, + "commit": "5922d170" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1779647497143.json b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1779647497143.json new file mode 100644 index 000000000..f12a63512 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1779647497143.json @@ -0,0 +1,125 @@ +{ + "transactions": [ + { + "hash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x66241", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x0000000000000000000000003c0563d7a197037d1bc599ae6ab8892438f16353", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000002c9721b4e6ac000000000000000000000000000000000000000000000000000f43fc2c04ee0000000000000000000000000000000000000000000000004c902b8aacf459b971120000000000000000000000000000000000000000000000000f17650a50075400000000000000000000000000000000000000000000004c902bb744160ea01d12", + "blockHash": "0xb946c83a9c92f39977283dd216ff3c82908acd0c60d975adbaeafa5b310faa3d", + "blockNumber": "0x2523f48", + "blockTimestamp": "0x6a134409", + "transactionHash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000020000000000000000000000000000000000080000000008000000000000000000000020000000440000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", + "transactionIndex": "0x0", + "blockHash": "0xb946c83a9c92f39977283dd216ff3c82908acd0c60d975adbaeafa5b310faa3d", + "blockNumber": "0x2523f48", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x6fc23ac3f", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x50591f", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x0000000000000000000000003c0563d7a197037d1bc599ae6ab8892438f16353", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000023139a6af1dd4000000000000000000000000000000000000000000000000000f17650a4e752601000000000000000000000000000000000000000000004c902bb744160ea01d120000000000000000000000000000000000000000000000000ce62b639f575201000000000000000000000000000000000000000000004c902de87dbcbdbdf112", + "blockHash": "0x0ac00020966fcedad43bf8917e06916cf68db4603299e3c3824a92f52eb72c97", + "blockNumber": "0x2523f49", + "blockTimestamp": "0x6a13440a", + "transactionHash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000020000000000000000000000000000000000080000000008000000000000000000000020000000440000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", + "transactionIndex": "0x0", + "blockHash": "0x0ac00020966fcedad43bf8917e06916cf68db4603299e3c3824a92f52eb72c97", + "blockNumber": "0x2523f49", + "gasUsed": "0x50591f", + "effectiveGasPrice": "0x6fc23ac3f", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779647497143, + "chain": 80002, + "commit": "b88d511c" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json index 7434592c5..b32cc9d0c 100644 --- a/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json +++ b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json @@ -1,92 +1,41 @@ { "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", + "hash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", "transactionType": "CREATE", "contractName": "ECDSAValidator.channelhub", - "contractAddress": "0x2a35728cadd8076dfd424fc3e20974a3cd03bfa5", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", "function": null, "arguments": null, "transaction": { - "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", - "gas": "0x880d5", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x84c87", "value": "0x0", - "input": "0x608080604052346015576106d6908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63600109bb14610024575f80fd5b346100cc5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100cc5760243567ffffffffffffffff81116100cc576100739036906004016100d0565b9060443567ffffffffffffffff81116100cc576100949036906004016100d0565b6064359173ffffffffffffffffffffffffffffffffffffffff831683036100cc576020946100c4946004356101a0565b604051908152f35b5f80fd5b9181601f840112156100cc5782359167ffffffffffffffff83116100cc57602083818601950101116100cc57565b90601f601f19910116810190811067ffffffffffffffff82111761012157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161012157601f01601f191660200190565b9291926101768261014e565b9161018460405193846100fe565b8294818452818301116100cc578281602093845f960137010152565b929091949383156102635773ffffffffffffffffffffffffffffffffffffffff85161561023b5761022060806101de6102279561022d99369161016a565b95601f19601f6020604051998a94828601526040808601528051918291826060880152018686015e5f858286010152011681010301601f1981018652856100fe565b369161016a565b9061028b565b1561023757600190565b5f90565b7f4501a919000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe1b97cf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156104cc575b806d04ee2d6d415b85acef8100000000600a9210156104b1575b662386f26fc1000081101561049d575b6305f5e10081101561048c575b61271081101561047d575b606481101561046f575b1015610465575b6001850190600a602161033461031e8561014e565b9461032c60405196876100fe565b80865261014e565b97601f19602086019901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530490811561038057600a90610345565b505073ffffffffffffffffffffffffffffffffffffffff5f9361040c86610415946020610404869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f1981018352826100fe565b5190206104f4565b9094919461052e565b169416841461045c5773ffffffffffffffffffffffffffffffffffffffff9261044d92610444925190206104f4565b9092919261052e565b1614610457575f90565b600190565b50505050600190565b9360010193610309565b606460029104960195610302565b612710600491049601956102f8565b6305f5e100600891049601956102ed565b662386f26fc10000601091049601956102e0565b6d04ee2d6d415b85acef8100000000602091049601956102d0565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046102b6565b81519190604183036105245761051d9250602082015190606060408401519301515f1a90610606565b9192909190565b50505f9160029190565b60048110156105d95780610540575050565b60018103610570577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b600281036105a457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003146105ae5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610695579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561068a575f5173ffffffffffffffffffffffffffffffffffffffff81161561068057905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea2646970667358221220c8e32dfe4c3317faffb02d4b02fddbb5e01dbc789e117442dd5ec08557786de764736f6c634300081e0033", - "nonce": "0x5", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", "chainId": "0x13882" }, "additionalContracts": [], "isFixedGasLimit": false }, { - "hash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "hash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", "transactionType": "CREATE", "contractName": "ChannelHub", - "contractAddress": "0x55d6f0a0322606447fbc612cf58014faed65af9d", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", "function": null, "arguments": [ - "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5" + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" ], "transaction": { - "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", - "gas": "0x6a626e", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x686f97", "value": "0x0", - "input": "0x60a0346100aa57601f61608238819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615fbf90816100c382396080518181816111420152613f180152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806316b390b11461024457806317536c061461023f578063187576d81461023a5780633115f6301461023557806338a66be21461023057806341b660ef1461022b57806347de477a146102265780635326919814610221578063587675e81461021c5780635a0745b4146102175780635b9acbf9146102125780635dc46a741461020d5780636840dbd2146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f457806382d3e15d146101ef5780638d0b12a5146101ea57806394191051146101e55780639691b468146101e0578063a5c82680146101db578063b00b6fd6146101d6578063b25a1d38146101d1578063beed9d5f146101cc578063c74a2d10146101c7578063d888ccae146101c2578063dc23f29e146101bd578063dd73d494146101b8578063e617208c146101b3578063ecf3d7e8146101ae578063f4ac51f5146101a9578063f766f8d6146101a4578063ff5bc09e1461019f5763ffa1ad741461019a575f80fd5b612650565b612639565b61249a565b61241f565b61230d565b61226e565b6120f0565b611ef5565b611db5565b611b71565b6119d6565b6116c4565b61165b565b6114ba565b611379565b61135c565b6111cb565b6111ae565b611171565b61112d565b611112565b611026565b61100f565b610fc8565b610fa6565b610f8a565b610f44565b610cfc565b610b13565b610850565b6107ea565b61065e565b6105d8565b6104ca565b6102cb565b9181601f840112156102775782359167ffffffffffffffff8311610277576020838186019501011161027757565b5f80fd5b60643590600282101561027757565b90606060031983011261027757600435916024359067ffffffffffffffff8211610277576102ba91600401610249565b909160443560028110156102775790565b34610277576102d93661028a565b6103986102f1859493945f52600260205260405f2090565b9283546102ff81151561266b565b61035a600286019461032a61031b87546001600160a01b031690565b948560038a019a8b5492613eff565b9591600160068b019a019661034a88546001600160a01b039060081c1690565b926103548c6128a0565b8861405d565b60c061036588614161565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301612a25565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80196610436956080955f9461044b575b50836104146104066104279697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104208a6128a0565b908c61428d565b015167ffffffffffffffff1690565b9061044660405192839283612a41565b0390a2005b6104279450946104146104786104069760c03d60c011610481575b6104708183612707565b810190612950565b955050946103e8565b503d610466565b612a36565b6001600160a01b0381160361027757565b6003196060910112610277576004356104b68161048d565b906024356104c38161048d565b9060443590565b6001600160a01b036104db3661049e565b92909116906104eb821515612b9e565b6104f6831515612bcd565b815f52600660205261051c8160405f20906001600160a01b03165f5260205260405f2090565b80549184830180931161059a577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b03925561055d615810565b61056885823361458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b612bfc565b60206040818301928281528451809452019201905f5b8181106105c25750505090565b82518452602093840193909201916001016105b5565b34610277576020600319360112610277576001600160a01b036004356105fd8161048d565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b818110610648576106448561063881870382612707565b6040519182918261059f565b0390f35b8254845260209093019260019283019201610621565b3461027757602060031936011261027757600354600480549190355f5b828410806107e1575b156107d4576106b06106a2610698866131a0565b90549060031b1c90565b5f52600260205260405f2090565b6001810160036106c1825460ff1690565b6106ca81611c00565b146107c2576106d882615b4d565b1561077e57915f8261076961077595600561076f96019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254612c10565b9055600360ff19825416179055565b556139c6565b936139c6565b915b919261067b565b505092905061078d9150600455565b8061079457005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b5050926107ce906139c6565b91610777565b92905061078d9150600455565b50818110610684565b34610277575f600319360112610277576020604051620186a08152f35b90816102609103126102775790565b90600319820160e081126102775760c0136102775760049160c4359067ffffffffffffffff82116102775761084d91600401610807565b90565b61085936610816565b906020820191600261086a84612c27565b61087381611c0f565b148015610af8575b8015610ada575b61088b90612c31565b61091a6108a061089b3685612c79565b614780565b916108aa8461483e565b60208401906108b882612ced565b956108d760408701976108ca89612ced565b608089013591858961494b565b60c0826108ff6108f86108ec6107148c612ced565b61073d60808501612ced565b54886149c5565b6040519687928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af493841561048857610a156001600160a01b0394610a2d936109967fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a1b955f91610aab575b50610985368d612c79565b61098f368a61301c565b908c614b52565b6109c2896109bd6109a685612ced565b6001600160a01b03165f52600160205260405f2090565b615bde565b5060026109ce82612c27565b6109d781611c0f565b03610a325750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a0d89826130ca565b0390a2612ced565b97612ced565b918360405194859416981696836130db565b0390a4005b610a3d600391612c27565b610a4681611c0f565b03610a7b57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a0d89826130ca565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a0d89826130ca565b610acd915060c03d60c011610ad3575b610ac58183612707565b810190612cf7565b5f61097a565b503d610abb565b5061088b610ae784612c27565b610af081611c0f565b159050610882565b506003610b0484612c27565b610b0d81611c0f565b1461087b565b610b1c36610816565b90610b3d6004610b2e60208501612c27565b610b3781611c0f565b14612c31565b610b4a61089b3683612c79565b9160208201610b5881612ced565b90610b7960408501926080610b6c85612ced565b960135958691868961494b565b610b8b610b858461316b565b86614c6c565b93610b9586614c9c565b15610bdd57505050610bd881610bcc7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f9809386614cf9565b604051918291826130ca565b0390a3005b610c259060c085610bf18897959697614161565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401613175565b038173728904e52308213ba61c90ef49f34c18fbda9e115af48015610488577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610bd895610c9a945f93610ca3575b50610c82610c8891612ced565b91612ced565b91610c93368761301c565b8a8a61428d565b610bcc846131ef565b610c88919350610cc4610c829160c03d60c011610481576104708183612707565b939150610c75565b90604060031983011261027757600435916024359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610d0a36610ccc565b610d1b6009610b2e60208401612c27565b610d376001610d31845f525f60205260405f2090565b0161323d565b610dfd610d4e60208301516001600160a01b031690565b9161071460c060408301610d7a610d6c82516001600160a01b031690565b608086015190888a8c61494b565b610de2610ddb610dc4610d8d368b61301c565b9586946101408c018d8d610da08361316b565b67ffffffffffffffff1646149d8e610eb7575b50505050516001600160a01b031690565b6060840151602001516001600160a01b031661073d565b54896149c5565b6040519586928392632a2d120f60e21b8452600484016132c9565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857610e2f935f93610e96575b5086614b52565b15610e65576104467f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c391604051918291826130ca565b6104467f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c82691604051918291826130ca565b610eb091935060c03d60c011610ad357610ac58183612707565b915f610e28565b610edb610f1092610ecc610f15963690612f3c565b60608d01526060369101612f3c565b60808b0152610ee86132b5565b60a08b0152610ef56132b5565b8b8b01526001600160a01b03165f52600160205260405f2090565b615c88565b505f8d8d82610db3565b600319604091011261027757600435610f378161048d565b9060243561084d8161048d565b34610277576020610f816001600160a01b03610f5f36610f1f565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610277575f600319360112610277576020604051612a308152f35b3461027757604060031936011261027757610644610638602435600435613373565b610fda610fd436610ccc565b9061342d565b005b60606003198201126102775760043591602435916044359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610fda61102036610fdc565b9161378a565b34610277576020600319360112610277576001600160a01b0360043561104b8161048d565b165f52600160205261105f60405f20615b05565b5f905f5b81518110156110ff5761109161108a61107c838561335f565b515f525f60205260405f2090565b5460ff1690565b61109a816121c1565b600381141590816110ea575b506110b4575b600101611063565b916110c78184600193106110cf576139c6565b9290506110ac565b6110d9858561335f565b516110e4828661335f565b526139c6565b600591506110f7816121c1565b14155f6110a6565b506106449181526040519182918261059f565b34610277575f60031936011261027757602060405160408152f35b34610277575f600319360112610277576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610277576020610f816001600160a01b0361118c36610f1f565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610277575f600319360112610277576020600454604051908152f35b34610277576112556111dc3661028a565b929391906111f2855f52600560205260405f2090565b9182549261120184151561266b565b600281019060a061122261121c84546001600160a01b031690565b8a615053565b604051809881927f24063eba000000000000000000000000000000000000000000000000000000008352600483016139d4565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4958615610488575f9661132b575b50600181015460081c6001600160a01b0316968792546112a2906001600160a01b031690565b809581956003850154976112b7928992613eff565b9a9190946006019a6112c88c6128a0565b956112d3968b61405d565b846112dd876128a0565b6112e795896150bb565b6060015167ffffffffffffffff166040519182916113059183612a41565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61134e91965060a03d60a011611355575b6113468183612707565b8101906136bc565b945f61127c565b503d61133c565b34610277575f600319360112610277576020604051620151808152f35b61143661138536610ccc565b6113a661139760208395949501612c27565b6113a081611c0f565b15612c31565b6113bc6001610d31855f525f60205260405f2090565b9060c08161141b6114146108ec6107146113e060208901516001600160a01b031690565b6114078b8a60408101938960806113fe87516001600160a01b031690565b9301519361494b565b516001600160a01b031690565b54876149c5565b6040519586928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361044693610bcc925f92611499575b50611492368561301c565b9087614b52565b6114b391925060c03d60c011610ad357610ac58183612707565b905f611487565b34610277576114c836610816565b906114da6006610b2e60208501612c27565b6114e761089b3683612c79565b91602082016114f581612ced565b9061150960408501926080610b6c85612ced565b611515610b858461316b565b9361151f86614c9c565b1561155657505050610bd881610bcc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614cf9565b6115a59060a08561157261156c87989697612ced565b89615053565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613773565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af48015610488577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610bd895610bcc945f93611614575b50610c8261160291612ced565b9161160d368761301c565b8a8a6150bb565b611602919350611635610c829160a03d60a011611355576113468183612707565b9391506115f5565b6024359060ff8216820361027757565b359060ff8216820361027757565b34610277576040600319360112610277576001600160a01b036116a96004356116838161048d565b8261168c61163d565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102775760043560243567ffffffffffffffff8111610277576116f3903690600401610807565b60443567ffffffffffffffff811161027757611713903690600401610249565b919061171d61027b565b9061172f855f525f60205260405f2090565b9161173c6001840161323d565b9161176661174b855460ff1690565b611754816121c1565b600181149081156119c2575b506139e5565b86611773600586016128a0565b916117b46117808861316b565b67ffffffffffffffff6117ab61179e875167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613a14565b60208501516001600160a01b0316976117d760408701516001600160a01b031690565b9367ffffffffffffffff6117ff61179e6117f08c61316b565b935167ffffffffffffffff1690565b9116116118c3575b94611867889795857f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9b6118616118859760149d61185561187c996118959c6118b49f60808c015192613eff565b9391949092369061301c565b9061405d565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613a43565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b61044660405192839283613a65565b909296959397946118df61190a9389888a60808601519361494b565b60c08761141b6119036108ec8c6001600160a01b03165f52600660205260405f2090565b548d6149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4938415610488577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118b4986118618e8c61185560149f976118679861187c9b61198a6118959f6118859f5f916119a3575b508d611983368961301c565b9089615443565b9a9f5050995050509750509b5095509597985050611807565b6119bc915060c03d60c011610ad357610ac58183612707565b5f611977565b600491506119cf816121c1565b145f611760565b34610277576080600319360112610277576004356119f38161048d565b6119fb61163d565b90604435611a088161048d565b60643567ffffffffffffffff811161027757611b4a6001600160a01b0392611b22611a6396611b07611b02611a4289973690600401610249565b60ff85169a91611afc90611a578d1515613a8d565b8b89169d8e1515612b9e565b611abf8785611ab9611aad611aad611aa085611a90866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613abc565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611af5608085612707565b3691612fcb565b9061564c565b613b01565b611a90856001600160a01b03165f52600760205260405f2090565b906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611b91611b7d36610ccc565b6113a66003610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361044693610bcc925f926114995750611492368561301c565b634e487b7160e01b5f52602160045260245ffd5b60041115611c0a57565b611bec565b600a1115611c0a57565b90600a821015611c0a5752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61084d9167ffffffffffffffff8251168152611c6f60208301516020830190611c19565b60408201516040820152611cdd6060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611d5f60a0840151610260610220850152610260840190611c26565b92015190610240818403910152611c26565b929367ffffffffffffffff60c09561084d98979482948752611d9281611c00565b602087015216604085015216606083015260808201528160a08201520190611c4b565b3461027757602060031936011261027757600435611dd1613b66565b505f52600260205260405f20611de561272a565b9080548252610644600182015491611e31611e21611e038560ff1690565b94611e12602088019687613baa565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e58611e4860028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611e90905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611ee46117f0611ec0600660058501549460e08701958652016128a0565b93610100810194855251965197611ed689611c00565b5167ffffffffffffffff1690565b905191519260405196879687611d71565b3461027757611f0336610816565b611f146008610b2e60208401612c27565b80611f89611f2561089b3686612c79565b936020810160c0611f3582612ced565b91611f546040850193611f4785612ced565b608087013591898c61494b565b610de2610ddb610dc4610714611f6a368b61301c565b9687958d8a611f7882614c9c565b9d8e15612052575b50505050612ced565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857611fc6935f9361202d575b50611fc0903690612c79565b86614b52565b15611ffc576104467f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a91604051918291826130ca565b6104467f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b891604051918291826130ca565b611fc091935061204b9060c03d60c011610ad357610ac58183612707565b9290611fb4565b6120aa936120876109a6926120696109bd9561483e565b8c606061207a366101408501612f3c565b9101526060369101612f3c565b60808c01526120946132b5565b60a08c01526120a16132b5565b8c8c0152612ced565b505f8d8a8e611f80565b9160a09367ffffffffffffffff9161084d97969385526120d381611c00565b602085015216604083015260608201528160808201520190611c4b565b346102775760206003193601126102775760043561210c613b66565b505f52600560205260405f2061212061273c565b908054825261064460018201549161213e611e21611e038560ff1690565b612155611e4860028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121b061219b600660058501549460c08501958652016128a0565b9160e0810192835251945195611ed687611c00565b9151905191604051958695866120b4565b60061115611c0a57565b906006821015611c0a5752565b9192612250610120946121f285612263959a99989a6121cb565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c4b565b946101008201520152565b34610277576020600319360112610277576004355f60a0604051612291816126ae565b82815282602082015282604082015282606082015282608082015201526122b6613b66565b505f525f6020526122c960405f20613bc2565b80516122d4816121c1565b61064460208301519260408101519060606122fd61179e608084015167ffffffffffffffff1690565b91015191604051958695866121d8565b346102775761231b3661049e565b90916123316001600160a01b0382161515612b9e565b61233c821515612bcd565b335f5260066020526123628360405f20906001600160a01b03165f5260205260405f2090565b54908282106123f75782820391821161059a578383916123b7936123b18361239b336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55614d9d565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610bd83394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b61243f61242b36610ccc565b6113a66002610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361044693610bcc925f926114995750611492368561301c565b34610277576124a836610f1f565b6124b0615810565b6001600160a01b038116916124c6831515612b9e565b6001600160a01b036124ed8261239b336001600160a01b03165f52600860205260405f2090565b54916124fa831515612bcd565b5f61251a8261239b336001600160a01b03165f52600860205260405f2090565b55169181836125945761253d915f808080858a5af1612537613c20565b50613c4f565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610fda60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f511481161561261a575b60409190915261253d577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612630573d15843b151516166125db565b503d5f823e3d90fd5b3461027757610fda61264a36610fdc565b91613c90565b34610277575f60031936011261027757602060405160018152f35b1561267257565b7fc60f1e78000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176126ca57604052565b61269a565b60e0810190811067ffffffffffffffff8211176126ca57604052565b60a0810190811067ffffffffffffffff8211176126ca57604052565b90601f601f19910116810190811067ffffffffffffffff8211176126ca57604052565b6040519061273a61012083612707565b565b6040519061273a61010083612707565b6040519061273a60e083612707565b90604051612768816126cf565b60c0600482946127a660ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c921680156127f9575b60208310146127e557565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127da565b5f9291815491612812836127cb565b8083529260018116908115612867575060011461282e57505050565b5f9081526020812093945091925b83831061284d575060209250010190565b60018160209294939454838587010152019101919061283c565b9050602094955060ff1991509291921683830152151560051b010190565b9061273a6128999260405193848092612803565b0383612707565b906040516128ad816126cf565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c0a57600d61291f9160c0936020860152600181015460408601526128f26002820161275b565b60608601526129036007820161275b565b6080860152612914600c8201612885565b60a086015201612885565b910152565b5190600482101561027757565b67ffffffffffffffff81160361027757565b5190811515820361027757565b908160c0910312610277576129b860a06040519261296d846126ae565b805184526020810151602085015261298760408201612924565b6040850152606081015161299a81612931565b606085015260808101516129ad81612931565b608085015201612943565b60a082015290565b9081516129cc81611c00565b815260a0806129ea602085015160c0602086015260c0850190611c4b565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b90602061084d9281815201906129c0565b6040513d5f823e3d90fd5b92916020612b8d61273a9360408752612a75815467ffffffffffffffff811660408a015260ff60608a019160401c16611c19565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d612b5b6102a08901600c8401612803565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612803565b94019067ffffffffffffffff169052565b15612ba557565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b15612bd457565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161059a57565b600a111561027757565b3561084d81612c1d565b15612c3857565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b63ffffffff81160361027757565b359061273a82612931565b91908260c091031261027757604051612c91816126ae565b60a08082948035612ca181612c60565b84526020810135612cb18161048d565b60208501526040810135612cc48161048d565b60408501526060810135612cd781612931565b6060850152608081013560808501520135910152565b3561084d8161048d565b908160c09103126102775760405190612d0f826126ae565b805182526020810151602083015260408101516006811015610277576129b89160a09160408501526060810151612d4581612931565b60608501526129ad60808201612943565b90612d628183516121cb565b608067ffffffffffffffff81612d87602086015160a0602087015260a0860190611c4b565b94604081015160408601526060810151606086015201511691015290565b359061273a82612c1d565b60c0809167ffffffffffffffff8135612dc881612931565b1684526001600160a01b036020820135612de18161048d565b16602085015260ff612df56040830161164d565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561027757016020813591019167ffffffffffffffff821161027757813603831361027757565b601f8260209493601f1993818652868601375f8582860101520116010190565b61084d9167ffffffffffffffff8235612e8a81612931565b168152612ea86020830135612e9e81612c1d565b6020830190611c19565b60408201356040820152612ec26060820160608401612db0565b612ed461014082016101408401612db0565b612f08612efc612ee8610220850185612e20565b610260610220860152610260850191612e52565b92610240810190612e20565b91610240818503910152612e52565b9091612f2e61084d93604084526040840190612d56565b916020818403910152612e72565b91908260e091031261027757604051612f54816126cf565b60c08082948035612f6481612931565b84526020810135612f748161048d565b6020850152612f856040820161164d565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116126ca57601f01601f191660200190565b929192612fd782612faf565b91612fe56040519384612707565b829481845281830111610277578281602093845f960137010152565b9080601f830112156102775781602061084d93359101612fcb565b919091610260818403126102775761303261274c565b9261303c82612c6e565b845261304a60208301612da5565b6020850152604082013560408501526130668160608401612f3c565b6060850152613079816101408401612f3c565b608085015261022082013567ffffffffffffffff8111610277578161309f918401613001565b60a085015261024082013567ffffffffffffffff8111610277576130c39201613001565b60c0830152565b90602061084d928181520190612e72565b60e09060a061084d949363ffffffff81356130f581612c60565b1683526001600160a01b03602082013561310e8161048d565b1660208401526001600160a01b03604082013561312a8161048d565b16604084015267ffffffffffffffff606082013561314781612931565b16606084015260808101356080840152013560a08201528160c08201520190612e72565b3561084d81612931565b9091612f2e61084d936040845260408401906129c0565b634e487b7160e01b5f52603260045260245ffd5b6003548110156131b85760035f5260205f2001905f90565b61318c565b80548210156131b8575f5260205f2001905f90565b916131eb9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156126ca57600181016003556003548110156131b85760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b9060405161324a816126ae565b60a0600382946001600160a01b03815463ffffffff8116865260201c1660208501526132a467ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906132c4602083612707565b5f8252565b90916132e061084d93604084526040840190612d56565b916020818403910152611c4b565b67ffffffffffffffff81116126ca5760051b60200190565b60405190613315602083612707565b5f808352366020840137565b9061332b826132ee565b6133386040519182612707565b828152601f1961334882946132ee565b0190602036910137565b9190820391821161059a57565b80518210156131b85760209160051b010190565b9190600354908084029380850482149015171561059a57818410156133f75783019081841161059a578082116133ef575b506133b76133b28483613352565b613321565b92805b8281106133c657505050565b806133d56106986001936131a0565b6133e86133e28584613352565b8861335f565b52016133ba565b90505f6133a4565b5050905061084d613306565b906006811015611c0a5760ff60ff198354169116179055565b90602061084d928181520190611c4b565b9061343f825f525f60205260405f2090565b61344b6001820161323d565b91613457825460ff1690565b9184613465600583016128a0565b91604086019261347c84516001600160a01b031690565b91600261349360208a01516001600160a01b031690565b9761349d816121c1565b1480613654575b6135995750506134e16108f86108ec6107146134fc9661140760c0978a978c898f6080906134d96001610b2e60208601612c27565b01519361494b565b6040519384928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4801561048857610f10613573946109a688937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613566965f92613578575b5061355f368961301c565b9086614b52565b50604051918291826130ca565b0390a2565b61359291925060c03d60c011610ad357610ac58183612707565b905f613554565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613573969195506136479450610f10926135fe6014836135e66109a695600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016136278151606061361d60208301516001600160a01b031690565b9101519086614d9d565b5160a061363e60208301516001600160a01b031690565b91015191614d9d565b506040519182918261341c565b5061366d61179e601483015467ffffffffffffffff1690565b42116134a4565b1561367b57565b7fdb1ea1ac000000000000000000000000000000000000000000000000000000005f5260045ffd5b906136ad81611c00565b60ff60ff198354169116179055565b908160a0910312610277576137116080604051926136d9846126eb565b80518452602081015160208501526136f360408201612924565b6040850152606081015161370681612931565b606085015201612943565b608082015290565b90815161372581611c00565b815260806001600160a01b038161374b602086015160a0602087015260a0860190611c4b565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612f2e61084d93604084526040840190613719565b9161379483614c9c565b613959576137aa825f52600560205260405f2090565b6137b684825414613674565b60018101918254916137d2836001600160a01b039060081c1690565b9360026137f26137eb828501546001600160a01b031690565b9560ff1690565b6137fb81611c00565b1480613939575b6138bf5750600361383b9161381e6007610b2e60208701612c27565b019261382e84548287868b61494b565b60a0836115728389615053565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4908115610488577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19561389995610bcc945f9461389e575b50549261160d368761301c565b0390a3565b6138b891945060a03d60a011611355576113468183612707565b925f61388c565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1945090613899936138fb610bcc93600360ff19825416179055565b613933600d60058401935f855495556139226004820167ffffffffffffffff198154169055565b015460401c6001600160a01b031690565b90614d9d565b5061395261179e600484015467ffffffffffffffff1690565b4211613802565b6138997f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d49891610bcc613992865f525f60205260405f2090565b6139be60026139af60018401546001600160a01b039060201c1690565b9201546001600160a01b031690565b908388615025565b5f19811461059a5760010190565b90602061084d928181520190613719565b156139ec57565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613a1b57565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161059a57565b9067ffffffffffffffff613a86602092959495604085526040850190612e72565b9416910152565b15613a9457565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613ac5575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613b0857565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613b3d826126cf565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b73826126cf565b606060c0835f81525f60208201525f6040820152613b8f613b30565b83820152613b9b613b30565b60808201528260a08201520152565b613bb382611c00565b52565b6006821015611c0a5752565b90604051613bcf816126eb565b608067ffffffffffffffff60148395613bec60ff82541686613bb6565b613bf86001820161323d565b6020860152613c09600582016128a0565b604086015260138101546060860152015416910152565b3d15613c4a573d90613c3182612faf565b91613c3f6040519384612707565b82523d5f602084013e565b606090565b15613c58575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b91613c9a83614c9c565b613e8157613cb0825f52600260205260405f2090565b613cbc84825414613674565b6001810191825491613cd8836001600160a01b039060081c1690565b936002613cf16137eb828501546001600160a01b031690565b613cfa81611c00565b1480613e5e575b613de457506003613d6591613d1d6005610b2e60208701612c27565b0192613d2d84548287868b61494b565b60c083610bf1613d5e613d51856001600160a01b03165f52600660205260405f2090565b61073d6101608501612ced565b5489614206565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9561389995610bcc945f94613dc3575b505492610c93368761301c565b613ddd91945060c03d60c011610481576104708183612707565b925f613db6565b7f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e94509061389993613e20610bcc93600360ff19825416179055565b613933600d60058401935f85549555613922600482017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b506004820154613e7a9060401c67ffffffffffffffff1661179e565b4211613d01565b6138997f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c91610bcc613992865f525f60205260405f2090565b15613ec3575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215613fd157843560f81c9081613f4c57507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150613f489050565b9091565b600180915f97939594975060ff86161c1603613fa957613f9d83613f8b611aa0613f4896611a908a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515613eba565b600101915f1990910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c0a57565b90816020910312610277575190565b939260609361404f6001600160a01b0394613a86949998998852608060208901526080880190611c26565b918683036040880152612e52565b906001600160a01b03929560209761409195996140c861407f61410d95615887565b6140ba604051998a928e840190613ff9565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f198101895288612707565b6140d18161400b565b61415a57505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b0392165afa80156104885761273a915f9161412b575b501515613b01565b61414d915060203d602011614153575b6141458183612707565b810190614015565b5f614123565b503d61413b565b90506140d7565b5f6040519161416f836126ae565b81835261420260208401614181613b66565b81526141f46040860191858352611e806004606089019288845260808a01958987526141bc60a08c01998b8b525f52600260205260405f2090565b9160ff6001840154166141ce81611c00565b8c526141dc600684016128a0565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614214836126ae565b5f835261420260208401614226613b66565b81526141f460408601915f8352611e80600460608901925f845260808a01955f87526141bc60a08c01995f8b525f52600260205260405f2090565b7f8000000000000000000000000000000000000000000000000000000000000000811461059a575f0390565b6020939291614329919796976142ab815f52600260205260405f2090565b976040860180516142bb81611c00565b6142c481611c00565b61450a575b5089888660a08901956142dc8751151590565b6144f6575b5050505050506142fc606085015167ffffffffffffffff1690565b67ffffffffffffffff81166144cb575b50608084015167ffffffffffffffff168061447c575b5051151590565b1561446357608001518201516001600160a01b031680935b8251905f82131561442357614363915061435b8451615ad0565b928391614527565b61437260058601918254612c10565b90555b0180515f8113156143d15750916143b060059261239b6143986143c69651615ad0565b966001600160a01b03165f52600660205260405f2090565b6143bb858254613352565b905501918254612c10565b90555b61273a61467e565b9290505f83126143e5575b505050506143c9565b61440260059261239b6143986143fd61441897614261565b615ad0565b61440d858254612c10565b905501918254613352565b90555f8080806143dc565b5f8212614433575b505050614375565b6144426143fd61444a93614261565b928391614d9d565b61445960058601918254613352565b9055825f8061442b565b50600d84015460401c6001600160a01b03168093614341565b6144c59060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614322565b6144f090600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f61430c565b6144ff956159a2565b5f80898886836142e1565b614521905161451881611c00565b60018b016136a3565b5f6142c9565b9061453a9291614535615810565b61458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b1561456757565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614679576001600160a01b0316918215801561466a576145b3823414614560565b156145bd57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614654575b6040919091525f606052156146205750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612630573d15833b1515161661460e565b6146743415614560565b6145b3565b505050565b6003546004545f5b82821080614776575b1561476b576146a36106a2610698846131a0565b6001810160036146b4825460ff1690565b6146bd81611c00565b14614759576146cb82615b4d565b1561471657915f8261076961470d95600561470796019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b916139c6565b915b9190614686565b5050915061472390600455565b8061472b5750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614765906139c6565b9161470f565b915061472390600455565b506040811061468f565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f01000000000000000000000000000000000000000000000000000000000000009160405161482760208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c0815261483660e082612707565b519020161790565b602081013561484c8161048d565b6001600160a01b03811690614862821515612b9e565b6040830135906148718261048d565b6148986001600160a01b0383169261488a841515612b9e565b6148938361048d565b61048d565b6148a18161048d565b5081146148ed575063ffffffff6201518091356148bd81612c60565b16106148c557565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b903590601e1981360301821215610277570180359067ffffffffffffffff82116102775760200191813603831361027757565b929161273a9461497c61498b92614971838761496b610220890189614918565b90613eff565b90878a949394615b81565b8361496b610240850185614918565b92909194615b81565b604051906149a1826126eb565b5f6080838281526149b0613b66565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916149da614994565b935f525f60205260405f20906149f460ff83541686613bb6565b614a00600583016128a0565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b169116178455614b4160018501614aef614ac660408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b92614b8e81614bde9460a094614b6f885f525f60205260405f2090565b97614b7b895460ff1690565b614b84816121c1565b15614c5a57615443565b604081018051614b9d816121c1565b614ba6816121c1565b151580614c2f575b614c15575b50601484018054606083015167ffffffffffffffff9081169116819003614bed575b50500151151590565b614be55750565b60135f910155565b614c0e919067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f80614bd5565b614c299051614c23816121c1565b85613403565b5f614bb3565b50845460ff16815190614c41826121c1565b614c4a826121c1565b614c53816121c1565b1415614bae565b614c678260018b01614a1f565b615443565b9067ffffffffffffffff604051916020830193845216604082015260408152614c96606082612707565b51902090565b805f525f60205260ff60405f2054166006811015611c0a578015908115614ce5575b50614ce0575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150614cf2816121c1565b145f614cbe565b90614d3b91614d146001610d31835f525f60205260405f2090565b60c0836108ff614d346108ec61071460408701516001600160a01b031690565b54856149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af49283156104885761273a945f94614d78575b50614d7290369061301c565b91614b52565b614d72919450614d969060c03d60c011610ad357610ac58183612707565b9390614d66565b9061453a9291614dab615810565b9190918115614679576001600160a01b0383169283614e4f576001600160a01b038216925f8080808488620186a0f1614de2613c20565b5015614def575050505050565b614e326138999261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614e3d828254612c10565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa908115610488575f91615006575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f91829190614ee7606482612707565b51908286620186a0f190614ef9613c20565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104885786915f93614fe5575b5083614fda575b83614fc6575b50505015614f5b575b50505050565b81614fa46001600160a01b039261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614faf858254612c10565b90556040519384521691602090a35f808080614f55565b614fd1929350613352565b145f8481614f4c565b818110159350614f46565b614fff91935060203d602011614153576141458183612707565b915f614f3f565b61501f915060203d602011614153576141458183612707565b5f614e92565b909192614d14614d3b946150456001610d31865f525f60205260405f2090565b92608084015191868661494b565b906001600160a01b0390615065614994565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661508d81611c00565b865261509b600682016128a0565b602087015260058101546040870152015416606084015216608082015290565b6020939291614329919796976150d9815f52600560205260405f2090565b976040860180516150e981611c00565b6150f281611c00565b615179575b50898886608089019561510a8751151590565b615165575b50505050505061512a606085015167ffffffffffffffff1690565b67ffffffffffffffff8116615140575051151590565b6144c590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b61516e95615d2e565b5f808988868361510f565b615187905161451881611c00565b5f6150f7565b90600a811015611c0a577fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000083549260401b169116179055565b9060c060049161520367ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116152b357505050565b5f5260205f20906020601f840160051c830193106152eb575b601f0160051c01905b8181106152e0575050565b5f81556001016152d5565b90915081906152cc565b919091825167ffffffffffffffff81116126ca5761531d8161531784546127cb565b846152a6565b6020601f82116001146153585781906131eb9394955f9261534d575b50508160011b915f199060031b1c19161790565b015190505f80615339565b601f1982169061536b845f5260205f2090565b915f5b8181106153a55750958360019596971061538d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615383565b9192602060018192868b01518155019401920161536e565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c0a5760c0600d916153fd61273a958561518d565b604081015160018501556154186060820151600286016151d0565b6154296080820151600786016151d0565b61543a60a0820151600c86016152f5565b015191016152f5565b6154596060919493945f525f60205260405f2090565b936154676080850151151590565b61563a575b01916154bf60a06154886020865101516001600160a01b031690565b9280515f81136155fc575b506020810180515f81136155b4575b5081515f8112615573575b50515f8112615528575b500151151590565b8061551a575b6154d6575b5050505061273a61467e565b61550f9261550260a0926154f6604060139601516001600160a01b031690565b90848451015191614d9d565b5101519201918254613352565b90555f8080806154ca565b5060a08351015115156154c5565b6143fd61553491614261565b61554f8561239b61071460408a01516001600160a01b031690565b61555a828254612c10565b905561556b60138901918254613352565b90555f6154b7565b6143fd61557f91614261565b61559d818761559860208b01516001600160a01b031690565b614d9d565b6155ac60138a01918254613352565b90555f6154ad565b6155bd90615ad0565b6155d88661239b61071460408b01516001600160a01b031690565b6155e3828254613352565b90556155f460138a01918254612c10565b90555f6154a2565b61560590615ad0565b615623818661561e60208a01516001600160a01b031690565b614527565b61563260138901918254612c10565b90555f615493565b61564781600587016153bd565b61546c565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156157e8575b806d04ee2d6d415b85acef8100000000600a9210156157cc575b662386f26fc100008110156157b7575b6305f5e1008110156157a5575b612710811015615795575b6064811015615786575b101561577b575b61571260216156da60018801615ddf565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561572257615712906156df565b50506001600160a01b036157478461573b858498615d73565b60208151910120615dc9565b9116931683146157735761576591816020611aad9351910120615dc9565b1461576e575f90565b600190565b505050600190565b6001909401936156c9565b600290606490049601956156c2565b60049061271090049601956156b8565b6008906305f5e10090049601956156ad565b601090662386f26fc1000090049601956156a0565b6020906d04ee2d6d415b85acef81000000009004960195615690565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615676565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00541461585f5760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff815116906020810151600a811015611c0a576159308260406159919401516158cf60806060840151930151946040519760208901526040880190611c19565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261084d61024082612707565b93909291935f52600260205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015192600a841015611c0a57615a5660c0615aa093615a0e615acc9760039a61518d565b60408101516007890155615a29606082015160088a016151d0565b615a3a6080820151600d8a016151d0565b615a4b60a082015160128a016152f5565b0151601387016152f5565b60018501907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b60028301906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0155565b5f8112615ada5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b818110615b3457505061273a92500383612707565b8454835260019485019487945060209093019201615b1f565b67ffffffffffffffff6004820154164210159081615b69575090565b600180925060ff91015416615b7d81611c00565b1490565b6001600160a01b039061410d615ba7615ba26020989599969799369061301c565b615887565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b6001810190825f528160205260405f2054155f14615c46578054680100000000000000008110156126ca57615c33615c1d8260018794018555846131bd565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c74575f190190615c6382826131bd565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d26575f19840184811161059a5783545f1981019490851161059a575f958583615ce397615cd69503615ce9575b505050615c4d565b905f5260205260405f2090565b55600190565b615d0f615d0991615d00610698615d1d95886131bd565b928391876131bd565b906131d2565b85905f5260205260405f2090565b555f8080615cce565b505050505f90565b93909291935f52600560205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b61273a90615dbb615db594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190613ff9565b90613ff9565b03601f198101845283612707565b61084d91615dd691615e06565b90929192615e40565b90615de982612faf565b615df66040519182612707565b828152601f196133488294612faf565b8151919060418303615e3657615e2f9250602082015190606060408401519301515f1a90615f07565b9192909190565b50505f9160029190565b615e4981611c00565b80615e52575050565b615e5b81611c00565b60018103615e8b577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e9481611c00565b60028103615ec857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ed4600392611c00565b14615edc5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f7e579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610488575f516001600160a01b03811615615f7457905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a1d82448e7e3f611b69660be3f5dc6e070ca23bb9e11aefc6fc6b7622d1cdc4e64736f6c634300081e00330000000000000000000000002a35728cadd8076dfd424fc3e20974a3cd03bfa5", - "nonce": "0x6", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", "chainId": "0x13882" }, "additionalContracts": [], @@ -95,184 +44,82 @@ ], "receipts": [ { + "type": "0x2", "status": "0x1", - "cumulativeGasUsed": "0x1410b0", + "cumulativeGasUsed": "0x66241", "logs": [ { "address": "0x0000000000000000000000000000000000001010", "topics": [ "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", "0x0000000000000000000000000000000000000000000000000000000000001010", - "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000003c0563d7a197037d1bc599ae6ab8892438f16353", "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" ], - "data": "0x000000000000000000000000000000000000000000000000008e12cca7c1e150000000000000000000000000000000000000000000000000a467417de9a33bd1000000000000000000000000000000000000000000002b297badcd163bced3e7000000000000000000000000000000000000000000000000a3d92eb141e15a81000000000000000000000000000000000000000000002b297c3bdfe2e390b537", - "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", - "blockNumber": "0x2144623", - "blockTimestamp": "0x69ac3613", - "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "data": "0x000000000000000000000000000000000000000000000000002c9721b4e6ac000000000000000000000000000000000000000000000000000f43fc2c04ee0000000000000000000000000000000000000000000000004c902b8aacf459b971120000000000000000000000000000000000000000000000000f17650a50075400000000000000000000000000000000000000000000004c902bb744160ea01d12", + "blockHash": "0xb946c83a9c92f39977283dd216ff3c82908acd0c60d975adbaeafa5b310faa3d", + "blockNumber": "0x2523f48", + "blockTimestamp": "0x6a134409", + "transactionHash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", "transactionIndex": "0x0", "logIndex": "0x0", "removed": false } ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", - "type": "0x2", - "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000020000000000000000000000000000000000080000000008000000000000000000000020000000440000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", "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", + "blockHash": "0xb946c83a9c92f39977283dd216ff3c82908acd0c60d975adbaeafa5b310faa3d", + "blockNumber": "0x2523f48", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x6fc23ac3f", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", "to": null, - "contractAddress": "0x2a35728cadd8076dfd424fc3e20974a3cd03bfa5" + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844" }, { + "type": "0x2", "status": "0x1", - "cumulativeGasUsed": "0x867909", + "cumulativeGasUsed": "0x50591f", "logs": [ { "address": "0x0000000000000000000000000000000000001010", "topics": [ "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", "0x0000000000000000000000000000000000000000000000000000000000001010", - "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000003c0563d7a197037d1bc599ae6ab8892438f16353", "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" ], - "data": "0x00000000000000000000000000000000000000000000000002436f597d48d070000000000000000000000000000000000000000000000000a2f28b238c978e23000000000000000000000000000000000000000000002b297d2283708be646ce000000000000000000000000000000000000000000000000a0af1bca0f4ebdb3000000000000000000000000000000000000000000002b297f65f2ca092f173e", - "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", - "blockNumber": "0x2144623", - "blockTimestamp": "0x69ac3613", - "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", - "transactionIndex": "0x4", - "logIndex": "0x4", + "data": "0x000000000000000000000000000000000000000000000000023139a6af1dd4000000000000000000000000000000000000000000000000000f17650a4e752601000000000000000000000000000000000000000000004c902bb744160ea01d120000000000000000000000000000000000000000000000000ce62b639f575201000000000000000000000000000000000000000000004c902de87dbcbdbdf112", + "blockHash": "0x0ac00020966fcedad43bf8917e06916cf68db4603299e3c3824a92f52eb72c97", + "blockNumber": "0x2523f49", + "blockTimestamp": "0x6a13440a", + "transactionHash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", + "transactionIndex": "0x0", + "logIndex": "0x0", "removed": false } ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", - "type": "0x2", - "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", - "transactionIndex": "0x4", - "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", - "blockNumber": "0x2144623", - "gasUsed": "0x51d590", - "effectiveGasPrice": "0x714a1d19e", - "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000800000000000000000000100000000000000000000000020000000000000000000000000000000000080000000008000000000000000000000020000000440000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", + "transactionIndex": "0x0", + "blockHash": "0x0ac00020966fcedad43bf8917e06916cf68db4603299e3c3824a92f52eb72c97", + "blockNumber": "0x2523f49", + "gasUsed": "0x50591f", + "effectiveGasPrice": "0x6fc23ac3f", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", "to": null, - "contractAddress": "0x55d6f0a0322606447fbc612cf58014faed65af9d" + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3" } ], "libraries": [ - "src/ChannelEngine.sol:ChannelEngine:0x78D150fdA6fa6739C18014B347c7c7C45C58e148", - "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x728904E52308213bA61C90EF49F34c18Fbda9E11", - "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x893F2D45fDFFe2D4297a5C1D5732EDce4849eE82" + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" ], "pending": [], "returns": {}, - "timestamp": 1772893715802, + "timestamp": 1779647497143, "chain": 80002, - "commit": "4b4244f1" + "commit": "b88d511c" } \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/8453/run-1779453955322.json b/contracts/broadcast/DeployChannelHub.s.sol/8453/run-1779453955322.json new file mode 100644 index 000000000..74d2fd865 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/8453/run-1779453955322.json @@ -0,0 +1,231 @@ +{ + "transactions": [ + { + "hash": "0x7bfeb3c5ea216c7628d479e323352d9089b308b692fbc143757aeb39eaa177d8", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x1e23a0663596972c73f7a984a70433dfd04def9d1eeaf99e9447ee56cbeffadd", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x815bd83c65c1c423b43476b69487acf8dd56adf60e05b0703dcedc86f0053716", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xcf1f6b75073fd56a8f258ec6cf3b511a05b653da99620518d192ecbfc5ef5bca", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x437caf63d9d3df4ecf493ac0645bcdac5304730848e9627f2ee0bb19a83d8511", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x9fc952", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x7bfeb3c5ea216c7628d479e323352d9089b308b692fbc143757aeb39eaa177d8", + "transactionIndex": "0x41", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x648ec", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0xadff", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xab97ea", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1e23a0663596972c73f7a984a70433dfd04def9d1eeaf99e9447ee56cbeffadd", + "transactionIndex": "0x43", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x45694", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x7818", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb661e4", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xcf1f6b75073fd56a8f258ec6cf3b511a05b653da99620518d192ecbfc5ef5bca", + "transactionIndex": "0x44", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x42b38", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x7363", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xbcc425", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x815bd83c65c1c423b43476b69487acf8dd56adf60e05b0703dcedc86f0053716", + "transactionIndex": "0x45", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x2b248", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x4aa9", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1c328d0", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x437caf63d9d3df4ecf493ac0645bcdac5304730848e9627f2ee0bb19a83d8511", + "transactionIndex": "0x94", + "blockHash": "0xb2803306108f08e76d09a6dcf439e6555a6b5faabfe33b9b45d23e30d1b3a5aa", + "blockNumber": "0x2c2f990", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x1f4df8", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x36268", + "l1Fee": "0x21f0887966", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779453955322, + "chain": 8453, + "commit": "e07ad9c2" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/8453/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/8453/run-latest.json new file mode 100644 index 000000000..11519db51 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/8453/run-latest.json @@ -0,0 +1,231 @@ +{ + "transactions": [ + { + "hash": "0x7bfeb3c5ea216c7628d479e323352d9089b308b692fbc143757aeb39eaa177d8", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x1e23a0663596972c73f7a984a70433dfd04def9d1eeaf99e9447ee56cbeffadd", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x815bd83c65c1c423b43476b69487acf8dd56adf60e05b0703dcedc86f0053716", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xcf1f6b75073fd56a8f258ec6cf3b511a05b653da99620518d192ecbfc5ef5bca", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "function": null, + "arguments": null, + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x437caf63d9d3df4ecf493ac0645bcdac5304730848e9627f2ee0bb19a83d8511", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "function": null, + "arguments": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "transaction": { + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e003300000000000000000000000098de11a97fdc08d057fc9ab310864655c49bbf8e000000000000000000000000bffaa37e34fb9aa11b23eb6cc939abbb45d6ccb6", + "nonce": "0x4", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x9fc952", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x7bfeb3c5ea216c7628d479e323352d9089b308b692fbc143757aeb39eaa177d8", + "transactionIndex": "0x41", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x648ec", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0xadff", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xab97ea", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x1e23a0663596972c73f7a984a70433dfd04def9d1eeaf99e9447ee56cbeffadd", + "transactionIndex": "0x43", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x45694", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x7818", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb661e4", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xcf1f6b75073fd56a8f258ec6cf3b511a05b653da99620518d192ecbfc5ef5bca", + "transactionIndex": "0x44", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x42b38", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x7363", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xbcc425", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x815bd83c65c1c423b43476b69487acf8dd56adf60e05b0703dcedc86f0053716", + "transactionIndex": "0x45", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x2c2f990", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x2b248", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x4aa9", + "l1Fee": "0x443473cd", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1c328d0", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x437caf63d9d3df4ecf493ac0645bcdac5304730848e9627f2ee0bb19a83d8511", + "transactionIndex": "0x94", + "blockHash": "0xb2803306108f08e76d09a6dcf439e6555a6b5faabfe33b9b45d23e30d1b3a5aa", + "blockNumber": "0x2c2f990", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x59a538", + "blobGasUsed": "0x1f4df8", + "from": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "to": null, + "contractAddress": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "l1GasPrice": "0x6b6312a", + "l1GasUsed": "0x36268", + "l1Fee": "0x21f0887966", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x5ce4e2", + "l1BlobBaseFeeScalar": "0x101c12", + "daFootprintGasScalar": "0x94" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779453955322, + "chain": 8453, + "commit": "e07ad9c2" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1776678417339.json b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1776678417339.json new file mode 100644 index 000000000..1eda2b42d --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1776678417339.json @@ -0,0 +1,229 @@ +{ + "transactions": [ + { + "hash": "0x7261a8f5f722c829e8d4381a519c2aa8c8fe689a7d5e1902494d7a74bd68426a", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x353a207a7bc822d8d3e58bca2f3f9e2b90b26e78", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x19549d", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea264697066735822122000e4e5e8c1be64955d884775f50f277bdf90eb640e1ce0e6ed58ae9fab8d8c9e64736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x7bb8823f66bdb3f9bef019607214000a7dc0eb0148a523dbce2c6709d42e7a74", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xaf9f833141094083b9de2cb6aa0f7e1f2d2deee1", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea2646970667358221220b8dfc2188e4a150cb46bb09f5a4154280dec2f13898ab33a6403028c1237d6b264736f6c634300081e0033", + "nonce": "0x1", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3a7095c50cafe072b66d893f4583722b03a14aba4cc8d9a3b359b88135028f5c", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xb5c2e8bad7417e654e9087753ec1d08762a06f91", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212201fd9878a14ac4e3f4864bf3741acbaf90b0e680274e02ca1b5a82ca18f3e6e2264736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x93cf089b7e57207fad34690b6525d97b78d4452eadddc3dd979b28224528b89a", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xce87fd88f4b5fd5475d163e2642c5c2c7dd655ec", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea264697066735822122095e45fd8b2cc47ea030c67fe68509a60a481b4484daf7ffd997c964f1bf115f464736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x470639b186a290226a188b8ee03cd26ddf38aedfdc2d09e269284f7cefdc2fcc", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x6e2c4707da119425df2c722e2695300154652f56", + "function": null, + "arguments": [ + "0xCe87FD88F4B5Fd5475d163e2642C5c2c7dD655Ec", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x675df2", + "value": "0x0", + "input": "0x60c03461010b57601f615de938819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615cc5908161012482396080518181816110c80152613e11015260a051818181610bab01528181610cbe01528181611387015281816119d601528181611fa40152818161354c01528181613fbe0152818161456f01526146510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461026f57806316b390b11461026a578063187576d8146102655780633115f6301461026057806338a66be21461025b5780633c684f921461025657806341b660ef1461025157806347de477a1461024c57806351bfcdbd1461024757806353269198146102425780635a0745b41461023d5780635ae2accc146102385780635b9acbf9146102335780635dc46a741461022e5780636840dbd2146102295780636898234b1461022457806371a471411461021f578063735181f01461021a57806382d3e15d146102155780638d0b12a5146102105780638e31c7351461020b57806394191051146102015780639691b46814610206578063a459463114610201578063a5c82680146101fc578063b25a1d38146101f7578063b65b78d1146101f2578063c74a2d10146101ed578063c9408398146101e8578063d888ccae146101e3578063d91a1283146101de578063dc23f29e146101d9578063dd73d494146101d4578063e617208c146101cf578063f4ac51f5146101ca578063f766f8d6146101c5578063ff5bc09e146101c05763ffa1ad74146101bb575f80fd5b6125dd565b6125c6565b6124a7565b61242c565b61238e565b61220c565b612055565b611f39565b611e30565b611ba7565b611b27565b611a38565b6116a7565b611548565b61141e565b61143b565b6112bb565b611174565b611157565b611111565b6110a9565b610fc4565b610fad565b610f62565b610f40565b610f25565b610f09565b610d11565b610c9f565b610af7565b6107fa565b610734565b6106f9565b61055d565b6104d7565b610341565b610289565b6001600160a01b0381160361028557565b5f80fd5b34610285576020366003190112610285576001600160a01b036004356102ae81610274565b165f526006602052602060405f2054604051908152f35b9181601f84011215610285578235916001600160401b038311610285576020838186019501011161028557565b60643590600282101561028557565b9060606003198301126102855760043591602435906001600160401b03821161028557610330916004016102c5565b909160443560028110156102855790565b34610285576103a36103dd61035536610301565b9294916103b8610370879693965f52600260205260405f2090565b9485549261037f8415156125f8565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613df0565b9192909901986103b28a612812565b87613f21565b60c06103c387614013565b604051809481926301999b9360e61b835260048301612982565b038173b5c2e8bad7417e654e9087753ec1d08762a06f915af48015610499577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610451946080945f93610466575b5082610443939461043c89612812565b908b614087565b01516001600160401b031690565b9061046160405192839283612abd565b0390a2005b610443935061048c9060c03d60c011610492575b610484818361268e565b8101906128c0565b9261042c565b503d61047a565b612993565b60206040818301928281528451809452019201905f5b8181106104c15750505090565b82518452602093840193909201916001016104b4565b34610285576020366003190112610285576001600160a01b036004356104fc81610274565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061054757610543856105378187038261268e565b6040519182918261049e565b0390f35b8254845260209093019260019283019201610520565b34610285576020366003190112610285576004355f905f60035491600454925b808410806106f0575b156106e5576105bb6105b56105a761059d87613074565b90549060031b1c90565b5f52600260205260405f2090565b936138d4565b946105c584615439565b6106d3576105d284615469565b15610690575f516020615c705f395f51905f526001600160a01b0361067961067361065e945f610610600c8b01546001600160a01b039060401c1690565b9961066d60016106318d6001600160a01b03165f52600660205260405f2090565b54928d610644600483019586549061324e565b9b8c916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556138d4565b976138d4565b604051938452951691602090a25b9391929361057d565b945050505061069e90600455565b806106a557005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b936106df9193506138d4565b91610687565b50505060045561069e565b50818310610586565b34610285575f366003190112610285576020604051620186a08152f35b6004359060ff8216820361028557565b359060ff8216820361028557565b346102855760203660031901126102855760ff61074f610716565b165f52600760205260405f2060405160408101918183106001600160401b038411176107ad576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b61260d565b90816102609103126102855790565b90600319820160e081126102855760c0136102855760049160c435906001600160401b038211610285576107f7916004016107b2565b90565b610803366107c1565b60208101600261081282612aee565b61081b81611c8f565b148015610adc575b8015610abe575b61083390612af8565b600261083e82612aee565b61084781611c8f565b03610aaf575b61090461086261085d3686612b3d565b61448f565b9261089061088161087a865f525f60205260405f2090565b5460ff1690565b61088a816122e2565b15612bb1565b61089c60208601612bc7565b906108a686614532565b6108b6608087013583838861460f565b60a0816108e96108e26108cb60808401612bc7565b6001600160a01b03165f52600660205260405f2090565b5488614676565b604051632a2d120f60e21b8152958692839260048401612def565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4908115610499577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e4946109796109fa936001600160a01b03965f91610a80575b50610968368b612b3d565b6109723686612ef3565b908a6147c2565b61099d87610998866001600160a01b03165f52600160205260405f2090565b61586d565b5060026109a982612aee565b6109b281611c8f565b036109ff5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f41778696206696604051806109e88582612f9f565b0390a25b604051938493169683612fb0565b0390a3005b610a0a600391612aee565b610a1381611c8f565b03610a5057857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a488582612f9f565b0390a26109ec565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a488582612f9f565b610aa2915060a03d60a011610aa8575b610a9a818361268e565b810190612bd1565b5f61095d565b503d610a90565b610ab93415612b0e565b61084d565b50610833610acb82612aee565b610ad481611c8f565b15905061082a565b506003610ae882612aee565b610af181611c8f565b14610823565b610b00366107c1565b90610b216004610b1260208501612aee565b610b1b81611c8f565b14612af8565b610b2a81614532565b610b3761085d3683612b3d565b916080610b4660208401612bc7565b92013591610b568382848761460f565b610b68610b628361303f565b856148d5565b92610b7285614904565b15610be85750506109fa7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610bd26001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613111565b610bdc8186614960565b60405191829182612f9f565b9091610c1460c082610bf987614013565b604051632ef10bcd60e21b8152938492839260048401613049565b038173b5c2e8bad7417e654e9087753ec1d08762a06f915af4928315610499577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca76946109fa94610c77935f91610c80575b50610c703686612ef3565b8989614087565b610bdc846130c3565b610c99915060c03d60c01161049257610484818361268e565b5f610c65565b34610285575f3660031901126102855760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102855760043591602435906001600160401b038211610285576107f7916004016107b2565b3461028557610d1f36610ce2565b610d306009610b1260208401612aee565b610d4c6001610d46845f525f60205260405f2090565b01613127565b610de7610d6360208301516001600160a01b031690565b91610d74608082015184868861460f565b610d7e3685612ef3565b61014085019386610d8e8661303f565b6001600160401b031646149586610ea1575b50505060a081610dcc610dc56108cb60206060850151016001600160a01b0390511690565b5489614676565b604051632a2d120f60e21b81529586928392600484016131b1565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af491821561049957610e19935f93610e80575b50866147c2565b15610e4f576104617f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182612f9f565b6104617f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182612f9f565b610e9a91935060a03d60a011610aa857610a9a818361268e565b915f610e12565b610f0092610eb3610efb923690612e14565b6060860152610ec53660608b01612e14565b6080860152610ed261319d565b60a0860152610edf61319d565b60c08601526001600160a01b03165f52600160205260405f2090565b615917565b505f8681610da0565b34610285575f366003190112610285576020604051612a308152f35b34610285575f36600319011261028557602060405160408152f35b346102855760403660031901126102855761054361053760243560043561327c565b3461028557610f79610f7336610ce2565b90613335565b005b6060600319820112610285576004359160243591604435906001600160401b038211610285576107f7916004016107b2565b3461028557610f79610fbe36610f7b565b91613685565b34610285576020366003190112610285576001600160a01b03600435610fe981610274565b165f526001602052610ffd60405f206157e1565b5f905f5b81518110156110965761102861087a61101a8385613268565b515f525f60205260405f2090565b611031816122e2565b60038114159081611081575b5061104b575b600101611001565b9161105e818460019310611066576138d4565b929050611043565b6110708585613268565b5161107b8286613268565b526138d4565b6005915061108e816122e2565b14155f61103d565b506105439181526040519182918261049e565b34610285575f3660031901126102855760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b60409060031901126102855760043561110481610274565b906024356107f781610274565b3461028557602061114e6001600160a01b0361112c366110ec565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610285575f366003190112610285576020600454604051908152f35b346102855761118236610301565b6111ce61119a859493945f52600560205260405f2090565b918254946111a98615156125f8565b60a06111b488614bcb565b604051809581926312031f5d60e11b8352600483016138e2565b038173af9f833141094083b9de2cb6aa0f7e1f2d2deee15af4908115610499577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103b296610451966060965f95611278575b50916112688596610443969385600561124c600161125c9901546001600160a01b039060081c1690565b97889360028401549a8b91613df0565b92909193019e8f612812565b61127189612812565b908b614c85565b610443955061125c939192966112a86112689260a03d60a0116112b4575b6112a0818361268e565b8101906135d4565b96509692919350611222565b503d611296565b34610285576060366003190112610285576112d4610716565b6024356112e081610274565b6044356001600160401b038111610285576113f3916113066113f89236906004016102c5565b9390946113b96113b460ff83169661131f8815156138f3565b6001600160a01b038616986113358a1515613909565b611376856113706113646113646113578460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b1561391f565b6113ae6113848b8730614d4d565b917f0000000000000000000000000000000000000000000000000000000000000000933691612ea2565b90614d85565b61393d565b6113d36113c46126af565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613953565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610285575f366003190112610285576020604051620151808152f35b34610285576114c461144c36610ce2565b61146d61145e60208395949501612aee565b61146781611c8f565b15612af8565b6114836001610d46855f525f60205260405f2090565b906114a861149b60208401516001600160a01b031690565b608084015190838761460f565b60a0816108e96114bd6108cb60808401612bc7565b5487614676565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361046193610bdc925f92611527575b506115203685612ef3565b90876147c2565b61154191925060a03d60a011610aa857610a9a818361268e565b905f611515565b3461028557611556366107c1565b906115686006610b1260208501612aee565b61157181614532565b61157e61085d3683612b3d565b91608061158d60208401612bc7565b9201359161159d8382848761460f565b6115a9610b628361303f565b926115b385614904565b156115e95750506109fa81610bdc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614960565b909161162560a08261160b6116046108cb6101608401612bc7565b5488614c28565b60405162ea54e760e01b815293849283926004840161366e565b038173af9f833141094083b9de2cb6aa0f7e1f2d2deee15af4928315610499577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f7946109fa94610bdc935f91611688575b506116813686612ef3565b8989614c85565b6116a1915060a03d60a0116112b4576112a0818361268e565b5f611676565b6080366003190112610285576004356024356001600160401b038111610285576116d59036906004016107b2565b6044356001600160401b038111610285576116f49036906004016102c5565b91906116fe6102f2565b92611710855f525f60205260405f2090565b91858461171f60018601613127565b9361172b865460ff1690565b93611735856122e2565b6001851494858015611a25575b61174b90612bb1565b61175760058901612812565b956117956117648661303f565b6001600160401b0361178c6117808b516001600160401b031690565b6001600160401b031690565b911610156139c1565b60208801516001600160a01b0316966001600160401b036117ce6117806117c060808d01519961303f565b93516001600160401b031690565b91161161188b575b5050946118317f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9989989661182b61185e9760149c61181f61184f996118469961187c9e613df0565b93919490923690612ef3565b90613f21565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b0342166139f7565b9301805467ffffffffffffffff19166001600160401b038516179055565b61046160405192839283613a17565b6119209495506004916118de916118c960208d9c9a9d9b969b01926118c460016118b486612aee565b6118bd81611c8f565b1415612af8565b6122e2565b80611a05575b6118d99015612af8565b612aee565b6118e781611c8f565b14806119d2575b6118f89015613111565b6119048489898d61460f565b60a0876108e96119196108cb60808401612bc7565b548d614676565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4918215610499577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9960149961182b8d8b61181f61185e9a6118319761187c9e61199d6118469c61184f9e5f916119b3575b506119963688612ef3565b8d896151af565b999e5099509950505097505096989950996117d6565b6119cc915060a03d60a011610aa857610a9a818361268e565b5f61198b565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118ee565b506118d96009611a1483612aee565b611a1d81611c8f565b1490506118cf565b50611a2f816122e2565b60048114611742565b604036600319011261028557600435611a5081610274565b6001600160a01b0360243591611a67831515613a37565b611a6f6154e4565b611a7a838233615378565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611b22575f516020615c705f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611b0f61046194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6131d6565b3461028557611b4c611b3836610ce2565b61146d6003610b1260208496959601612aee565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361046193610bdc925f9261152757506115203685612ef3565b34610285575f36600319011261028557600354600454905f805b82841015611c63577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611c0483615439565b611c5157611c1183615469565b15611c3a57611c31916004611c286105b5936138d4565b9401549061324e565b915b9192611bc1565b92509250505b604080519182526020820192909252f35b915092611c5d906138d4565b91611c33565b92509050611c40565b634e487b7160e01b5f52602160045260245ffd5b60041115611c8a57565b611c6c565b600a1115611c8a57565b90600a821015611c8a5752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6107f7916001600160401b038251168152611ced60208301516020830190611c99565b60408201516040820152611d5a6060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611ddb60a0840151610260610220850152610260840190611ca6565b92015190610240818403910152611ca6565b92936001600160401b0360c0956107f798979482948752611e0d81611c80565b602087015216604085015216606083015260808201528160a08201520190611cca565b3461028557602036600319011261028557600435611e4c613a83565b505f52600260205260405f2060405190611e6582612621565b80548252610543600182015491611eb0611ea0611e828560ff1690565b94611e91602088019687613ac7565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291611f286117c0611f06600560048501549460c0870195865201612812565b9360e0810194855251965197611f1b89611c80565b516001600160401b031690565b905191519260405196879687611ded565b3461028557606036600319011261028557600435611f5681610274565b5f516020615c705f395f51905f5261046160243592611f7484610274565b60443593611f8c6001600160a01b0383161515613909565b611f97851515613a37565b611fcb6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613ad3565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611b0f866120456001600160a01b038516988995865f5260066020526120228260405f205461201d82821015613ae9565b61325b565b978861203f836001600160a01b03165f52600660205260405f2090565b55615498565b6040519081529081906020820190565b3461028557612063366107c1565b6120746008610b1260208401612aee565b61208161085d3684612b3d565b916120e261209160208301612bc7565b916120a2608082013584868861460f565b6120ac3685612ef3565b6120b586614904565b938685156121ab575b505060a081610dcc610dc56108cb60206060850151016001600160a01b0390511690565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af49182156104995761211f935f93612186575b50612119903690612b3d565b866147c2565b15612155576104617f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182612f9f565b6104617f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182612f9f565b6121199193506121a49060a03d60a011610aa857610a9a818361268e565b929061210d565b6109986121c9926121bb86614532565b610eb3366101408b01612e14565b505f866120be565b9160a0936001600160401b03916107f797969385526121ef81611c80565b602085015216604083015260608201528160808201520190611cca565b3461028557602036600319011261028557600435612228613a83565b505f52600560205260405f20604051906122418261263d565b80548252610543600182015491612278611ea060ff851694602087019561226781611c80565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936122d16122bc600560048501549460a0850195865201612812565b9160c0810192835251945195611f1b87611c80565b9151905191604051958695866121d1565b60061115611c8a57565b906006821015611c8a5752565b91926123706101209461231385612383959a99989a6122ec565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b0360408201511660408501526001600160401b036060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611cca565b946101008201520152565b34610285576020366003190112610285576004355f60a06040516123b181612658565b82815282602082015282604082015282606082015282608082015201526123d6613a83565b505f525f6020526123e960405f20613b0b565b80516123f4816122e2565b610543602083015192604081015190606061241c61178060808401516001600160401b031690565b91015191604051958695866122f9565b61244c61243836610ce2565b61146d6002610b1260208496959601612aee565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361046193610bdc925f9261152757506115203685612ef3565b34610285576124b5366110ec565b6124bd6154e4565b6001600160a01b038116916124d3831515613909565b6001600160a01b03612510826124fa336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b549161251d831515613a37565b5f61253d826124fa336001600160a01b03165f52600860205260405f2090565b55169181836125b757612560915f808080858a5af161255a613b68565b50613b97565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610f7960017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6125c19184615542565b612560565b3461028557610f796125d736610f7b565b91613bbf565b34610285575f36600319011261028557602060405160018152f35b156125ff57565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b038211176107ad57604052565b60e081019081106001600160401b038211176107ad57604052565b60c081019081106001600160401b038211176107ad57604052565b60a081019081106001600160401b038211176107ad57604052565b90601f801991011681019081106001600160401b038211176107ad57604052565b604051906126be60408361268e565b565b604051906126be60e08361268e565b906040516126dc8161263d565b60c06004829461271960ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561276c575b602083101461275857565b634e487b7160e01b5f52602260045260245ffd5b91607f169161274d565b5f92918154916127858361273e565b80835292600181169081156127da57506001146127a157505050565b5f9081526020812093945091925b8383106127c0575060209250010190565b6001816020929493945483858701015201910191906127af565b915050602093945060ff929192191683830152151560051b010190565b906126be61280b9260405193848092612776565b038361268e565b9060405161281f8161263d565b809260ff81546001600160401b038116845260401c1690600a821015611c8a57600d6128909160c093602086015260018101546040860152612863600282016126cf565b6060860152612874600782016126cf565b6080860152612885600c82016127f7565b60a0860152016127f7565b910152565b5190600482101561028557565b6001600160401b0381160361028557565b5190811515820361028557565b908160c09103126102855761292860a0604051926128dd84612658565b80518452602081015160208501526128f760408201612895565b6040850152606081015161290a816128a2565b6060850152608081015161291d816128a2565b6080850152016128b3565b60a082015290565b90815161293c81611c80565b815260806001600160401b0381612962602086015160a0602087015260a0860190611cca565b946040810151604086015282606082015116606086015201511691015290565b9060206107f7928181520190612930565b6040513d5f823e3d90fd5b90600d6107f7926129c681546001600160401b038116855260ff602086019160401c16611c99565b60018101546040840152612a326060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612aad6102608401600c8301612776565b9261024081850391015201612776565b906001600160401b03612add60209295949560408552604085019061299e565b9416910152565b600a111561028557565b356107f781612ae4565b15612aff57565b633226144f60e21b5f5260045ffd5b15612b1557565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361028557565b35906126be826128a2565b91908260c091031261028557604051612b5581612658565b60a08082948035612b6581612b24565b84526020810135612b7581610274565b60208501526040810135612b8881610274565b60408501526060810135612b9b816128a2565b6060850152608081013560808501520135910152565b15612bb857565b631e40ad6360e31b5f5260045ffd5b356107f781610274565b908160a09103126102855760405190612be982612673565b80518252602081015160208301526040810151600681101561028557612c2a9160809160408501526060810151612c1f816128a2565b6060850152016128b3565b608082015290565b90612c3e8183516122ec565b60806001600160401b0381612c62602086015160a0602087015260a0860190611cca565b94604081015160408601526060810151606086015201511691015290565b35906126be82612ae4565b60c080916001600160401b038135612ca2816128a2565b1684526001600160a01b036020820135612cbb81610274565b16602085015260ff612ccf60408301610726565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102855701602081359101916001600160401b03821161028557813603831361028557565b908060209392818452848401375f828201840152601f01601f1916010190565b6107f7916001600160401b038235612d62816128a2565b168152612d806020830135612d7681612ae4565b6020830190611c99565b60408201356040820152612d9a6060820160608401612c8b565b612dac61014082016101408401612c8b565b612de0612dd4612dc0610220850185612cfa565b610260610220860152610260850191612d2b565b92610240810190612cfa565b91610240818503910152612d2b565b9091612e066107f793604084526040840190612c32565b916020818403910152612d4b565b91908260e091031261028557604051612e2c8161263d565b60c08082948035612e3c816128a2565b84526020810135612e4c81610274565b6020850152612e5d60408201610726565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b0381116107ad57601f01601f191660200190565b929192612eae82612e87565b91612ebc604051938461268e565b829481845281830111610285578281602093845f960137010152565b9080601f83011215610285578160206107f793359101612ea2565b9190916102608184031261028557612f096126c0565b92612f1382612b32565b8452612f2160208301612c80565b602085015260408201356040850152612f3d8160608401612e14565b6060850152612f50816101408401612e14565b60808501526102208201356001600160401b0381116102855781612f75918401612ed8565b60a08501526102408201356001600160401b03811161028557612f989201612ed8565b60c0830152565b9060206107f7928181520190612d4b565b60e09060a06107f7949363ffffffff8135612fca81612b24565b1683526001600160a01b036020820135612fe381610274565b1660208401526001600160a01b036040820135612fff81610274565b1660408401526001600160401b03606082013561301b816128a2565b16606084015260808101356080840152013560a08201528160c08201520190612d4b565b356107f7816128a2565b9091612e066107f793604084526040840190612930565b634e487b7160e01b5f52603260045260245ffd5b60035481101561308c5760035f5260205f2001905f90565b613060565b805482101561308c575f5260205f2001905f90565b916130bf9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156107ad576001810160035560035481101561308c5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b1561311857565b6370a8bfcd60e11b5f5260045ffd5b9060405161313481612658565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261318c6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b604051906131ac60208361268e565b5f8252565b90916131c86107f793604084526040840190612c32565b916020818403910152611cca565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116107ad5760051b60200190565b6040519061321060208361268e565b5f808352366020840137565b90613226826131ea565b613233604051918261268e565b8281528092613244601f19916131ea565b0190602036910137565b91908201809211611b2257565b91908203918211611b2257565b805182101561308c5760209160051b010190565b91906003549080840293808504821490151715611b22578184101561330057830190818411611b22578082116132f8575b506132c06132bb848361325b565b61321c565b92805b8281106132cf57505050565b806132de61059d600193613074565b6132f16132eb858461325b565b88613268565b52016132c3565b90505f6132ad565b505090506107f7613201565b906006811015611c8a5760ff80198354169116179055565b9060206107f7928181520190611cca565b90613347825f525f60205260405f2090565b61335360018201613127565b9161335f825460ff1690565b918461336d60058301612812565b91600261338460208801516001600160a01b031690565b9561338e816122e2565b148061357d575b6134a4575050506133ad6001610b1260208401612aee565b6133bd608084015183838761460f565b6133f060a0826133d56108e26108cb60808401612bc7565b604051632a2d120f60e21b8152938492839260048401612def565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4801561049957610efb61347e9461345a88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613471965f92613483575b506134533689612ef3565b90866147c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182612f9f565b0390a2565b61349d91925060a03d60a011610aa857610a9a818361268e565b905f613448565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061357092935061347e946135036014836134eb610efb95600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61345a6060860161352f8151606061352560208301516001600160a01b031690565b9101519085614a14565b5160a061354660208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614a14565b5060405191829182613324565b506014810154426001600160401b0390911610613395565b1561359c57565b6336c7a86b60e21b5f5260045ffd5b906135b581611c80565b60ff80198354169116179055565b9060206107f792818152019061299e565b908160a091031261028557612c2a6080604051926135f184612673565b805184526020810151602085015261360b60408201612895565b60408501526060810151612c1f816128a2565b90815161362a81611c80565b8152608080613648602085015160a0602086015260a0850190611cca565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612e066107f79360408452604084019061361e565b91613698825f52600560205260405f2090565b906136a38385614b7e565b61387c576136b384835414613595565b600182018054929060026136d6600886901c6001600160a01b03165b9560ff1690565b6136df81611c80565b1480613864575b61377d57506002906136ff6007610b1260208601612aee565b01549061370e8284838861460f565b61371d60a08261160b87614bcb565b038173af9f833141094083b9de2cb6aa0f7e1f2d2deee15af4928315610499577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461377894610bdc935f9161168857506116813686612ef3565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556137cf600383016001600160401b03198154169055565b5f516020615c705f395f51905f526001600160a01b03613822613800600c8601546001600160a01b039060401c1690565b9361381c856001600160a01b03165f52600660205260405f2090565b5461324e565b928361383f826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a2613854614389565b61377860405192839201826135c3565b506003820154426001600160401b03909116106136e6565b7f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d498915061377890610bdc6138cd60016138bc885f525f60205260405f2090565b015460201c6001600160a01b031690565b8287614ba0565b5f198114611b225760010190565b9060206107f792818152019061361e565b156138fa57565b6306ee4dcd60e01b5f5260045ffd5b1561391057565b63e6c4247b60e01b5f5260045ffd5b156139275750565b60ff906357470ffd60e01b5f521660045260245ffd5b1561394457565b63c1606c2f60e01b5f5260045ffd5b6001600160401b0360206126be936139986001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b156139c857565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611b2257565b906001600160401b03809116911601906001600160401b038211611b2257565b906001600160401b03612add602092959495604085526040850190612d4b565b15613a3e57565b6334b2073960e11b5f5260045ffd5b60405190613a5a8261263d565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613a908261263d565b606060c0835f81525f60208201525f6040820152613aac613a4d565b83820152613ab8613a4d565b60808201528260a08201520152565b613ad082611c80565b52565b15613ada57565b6308ad910960e21b5f5260045ffd5b15613af057565b631e9acf1760e31b5f5260045ffd5b6006821015611c8a5752565b90604051613b1881612673565b60806001600160401b0360148395613b3460ff82541686613aff565b613b4060018201613127565b6020860152613b5160058201612812565b604086015260138101546060860152015416910152565b3d15613b92573d90613b7982612e87565b91613b87604051938461268e565b82523d5f602084013e565b606090565b15613ba0575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613bd2825f52600260205260405f2090565b90613bdd838561559b565b613d5057613bed84835414613595565b60018201805492906002613c0d600886901c6001600160a01b03166136cf565b613c1681611c80565b1480613d2d575b613caf5750600290613c366005610b1260208601612aee565b015490613c458284838861460f565b613c5460c082610bf987614013565b038173b5c2e8bad7417e654e9087753ec1d08762a06f915af4928315610499577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461377894610bdc935f91610c805750610c703686612ef3565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613d259060048301905f82549255613d0e600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614a14565b613854614389565b50600382015460401c6001600160401b03166001600160401b0342911610613c1d565b7f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c915061377890610bdc6138cd60016138bc885f525f60205260405f2090565b15613d9757565b6306a41ced60e21b5f5260045ffd5b15613dae5750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613dcd575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613eca57801561308c57613e3f91843560f81c9081613e4357507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613e5684613e5d949060ff161c90565b1614613d90565b613ebd613e758260ff165f52600760205260405f2090565b546001600160a01b0381169290613eaa90613ea590613e9684871515613da6565b60a01c6001600160401b031690565b6139d7565b906001600160401b038216421015613dc4565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610285575190565b9392606093613f136001600160a01b0394612add949998998852608060208901526080880190611ca6565b918683036040880152612d2b565b9193929590613f2f906155b3565b916002821015611c8a576020956001600160a01b0392613fb857613f6b905b604051635850a09b60e11b81529889978896879560048701613ee8565b0392165afa8015610499576126be915f91613f89575b50151561393d565b613fab915060203d602011613fb1575b613fa3818361268e565b810190613ed9565b5f613f81565b503d613f99565b50613f6b7f0000000000000000000000000000000000000000000000000000000000000000613f4e565b60405190613fef82612673565b5f608083828152613ffe613a83565b60208201528260408201528260608201520152565b61401b613fe2565b905f5260026020526001600160401b0380600360405f2060ff60018201541661404381611c80565b855261405160058201612812565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611b22575f0390565b602093929161411b916140a2815f52600260205260405f2090565b976040860180516140b281611c80565b6140bb81611c80565b61436c575b50878560a08801946140d28651151590565b614359575b50505050506140f060608501516001600160401b031690565b6001600160401b038116614330575b5060808401516001600160401b0316806142f0575b5051151590565b156142d757608001518201516001600160a01b031680935b8251905f82131561429757614155915061414d84516157c5565b92839161536a565b6141646004860191825461324e565b90555b0180515f8113156141fc57505f516020615c705f395f51905f52916141946001600160a01b0392516157c5565b6141e560046141be836141b8866001600160a01b03165f52600660205260405f2090565b5461325b565b96876141db866001600160a01b03165f52600660205260405f2090565b550191825461324e565b90556040519384521691602090a25b6126be614389565b90505f811261420e575b5050506141f4565b5f516020615c705f395f51905f52916142366142316001600160a01b0393614077565b6157c5565b614281600461425a8361381c866001600160a01b03165f52600660205260405f2090565b9687614277866001600160a01b03165f52600660205260405f2090565b550191825461325b565b90556040519384521691602090a25f8080614206565b5f82126142a7575b505050614167565b6142b66142316142be93614077565b928391614a14565b6142cd6004860191825461325b565b9055825f8061429f565b50600c84015460401c6001600160a01b03168093614133565b61432a9060038901906fffffffffffffffff000000000000000082549160401b16906fffffffffffffffff00000000000000001916179055565b5f614114565b6143539060038901906001600160401b03166001600160401b0319825416179055565b5f6140ff565b614362946156cb565b5f808785826140d7565b614383905161437a81611c80565b60018b016135ab565b5f6140c0565b5f905f60035491600454925b80841080614485575b15614478576143b56105b56105a761059d87613074565b946143bf84615439565b614466576143cc84615469565b15614421575f516020615c705f395f51905f526001600160a01b0361440a61067361065e945f610610600c8b01546001600160a01b039060401c1690565b604051938452951691602090a25b93919293614395565b93919450506144309150600455565b806144385750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b936144729193506138d4565b91614418565b5092916144309150600455565b506040831061439e565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040519161451460208401809260a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b0360408201511660408501526001600160401b036060820151166060850152608081015160808501520151910152565b60c0835261452360e08461268e565b915190912016600160f81b1790565b6001600160a01b03602082013561454881610274565b16614554811515613909565b6001600160a01b03614599604084013561456d81610274565b7f0000000000000000000000000000000000000000000000000000000000000000831692168214613ad3565b81146145cb575063ffffffff6201518091356145b481612b24565b16106145bc57565b630596b15b60e01b5f5260045ffd5b63abfa558d60e01b5f5260045260245ffd5b903590601e198136030182121561028557018035906001600160401b0382116102855760200191813603831361028557565b90916126be9361463f61464d926146348361462e6102208901896145dd565b90613df0565b908888949394615829565b61462e6102408501856145dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615829565b9060146001600160401b039161468a613fe2565b935f525f60205260405f20906146a460ff83541686613aff565b6146b060058301612812565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556147b16001850161478461475b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926147fe8161484d946080946147df885f525f60205260405f2090565b976147eb895460ff1690565b6147f4816122e2565b156148c3576151af565b60408101805161480d816122e2565b614816816122e2565b151580614898575b61487e575b5060148401805460608301516001600160401b03908116911681900361485c575b50500151151590565b6148545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614844565b614892905161488c816122e2565b8561330c565b5f614823565b50845460ff168151906148aa826122e2565b6148b3826122e2565b6148bc816122e2565b141561481e565b6148d08260018b016146cf565b6151af565b906001600160401b036040519160208301938452166040820152604081526148fe60608261268e565b51902090565b805f525f60205260ff60405f2054166006811015611c8a57801590811561494c575b50614947575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614959816122e2565b145f614926565b906149b291805f525f60205261497b600160405f2001613127565b60a0836149976149906108cb60808401612bc7565b5485614676565b604051632a2d120f60e21b8152968792839260048401612def565b038173353a207a7bc822d8d3e58bca2f3f9e2b90b26e785af4928315610499576126be945f946149ef575b506149e9903690612ef3565b916147c2565b6149e9919450614a0d9060a03d60a011610aa857610a9a818361268e565b93906149dd565b90614a279291614a226154e4565b614a4d565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614b79576001600160a01b0383169283614af1576001600160a01b038216925f8080808488620186a0f1614a84613b68565b5015614a91575050505050565b614ad4613778926124fa7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614adf82825461324e565b90556040519081529081906020820190565b614b03614aff8484846159bd565b1590565b614b0e575b50505050565b81614b576001600160a01b03926124fa7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614b6285825461324e565b90556040519384521691602090a35f808080614b08565b505050565b905f52600560205260405f2054159081614b96575090565b6107f79150614904565b6149b29261497b614bbd6001610d46855f525f60205260405f2090565b91608083015190858561460f565b614bd3613fe2565b905f5260056020526001600160401b03600360405f2060ff600182015416614bfa81611c80565b8452614c0860058201612812565b60208501526004810154604085015201541660608201525f608082015290565b90614c31613fe2565b915f5260056020526001600160401b03600360405f2060ff600182015416614c5881611c80565b8552614c6660058201612812565b6020860152600481015460408601520154166060830152608082015290565b602093929161411b91614ca0815f52600560205260405f2090565b97604086018051614cb081611c80565b614cb981611c80565b614d39575b5087856080880194614cd08651151590565b614d26575b5050505050614cee60608501516001600160401b031690565b6001600160401b038116614d03575051151590565b61432a9060038901906001600160401b03166001600160401b0319825416179055565b614d2f94615a51565b5f80878582614cd5565b614d47905161437a81611c80565b5f614cbe565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526107f760a08261268e565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015614f21575b806d04ee2d6d415b85acef8100000000600a921015614f05575b662386f26fc10000811015614ef0575b6305f5e100811015614ede575b612710811015614ece575b6064811015614ebf575b1015614eb4575b614e4b6021614e1360018801615b0f565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614e5b57614e4b90614e18565b50506001600160a01b03614e8084614e74858498615aa3565b60208151910120615af9565b911693168314614eac57614e9e918160206113649351910120615af9565b14614ea7575f90565b600190565b505050600190565b600190940193614e02565b60029060649004960195614dfb565b6004906127109004960195614df1565b6008906305f5e1009004960195614de6565b601090662386f26fc100009004960195614dd9565b6020906d04ee2d6d415b85acef81000000009004960195614dc9565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614daf565b90600a811015611c8a5768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161502157505050565b5f5260205f20906020601f840160051c83019310615059575b601f0160051c01905b81811061504e575050565b5f8155600101615043565b909150819061503a565b91909182516001600160401b0381116107ad5761508a81615084845461273e565b84615014565b6020601f82116001146150c55781906130bf9394955f926150ba575b50508160011b915f199060031b1c19161790565b015190505f806150a6565b601f198216906150d8845f5260205f2090565b915f5b818110615112575095836001959697106150fa575b505050811b019055565b01515f1960f88460031b161c191690555f80806150f0565b9192602060018192868b0151815501940192016150db565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611c8a5760c0600d916151696126be9585614f49565b60408101516001850155615184606082015160028601614f76565b615195608082015160078601614f76565b6151a660a0820151600c8601615063565b01519101615063565b602060606151ea826151ce6151fb959896985f525f60205260405f2090565b976151dc8860058b0161512a565b01516001600160a01b031690565b94015101516001600160a01b031690565b809282515f811361533f575b50602083019283515f81136152be575b5051905f8212615296575b505050515f8112615239575b5050506126be614389565b5f516020615c705f395f51905f529161525c6142316001600160a01b0393614077565b615280601361425a8361381c866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061522e565b6142b66142316152a593614077565b6152b46013850191825461325b565b9055815f80615222565b6152c7906157c5565b6152e6816141b8866001600160a01b03165f52600660205260405f2090565b9081615303866001600160a01b03165f52600660205260405f2090565b556153136013890191825461324e565b90556040519081526001600160a01b038416905f516020615c705f395f51905f5290602090a25f615217565b615348906157c5565b61535381848461536a565b6153626013870191825461324e565b90555f615207565b90614a2792916153786154e4565b908215614b79576001600160a01b0316918215801561542a5761539c823414612b0e565b156153a657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561540b575b6040919091525f606052156153f05750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615421573d15833b151516166153de565b503d5f823e3d90fd5b6154343415612b0e565b61539c565b6001015460ff1661544981611c80565b60038114908115615458575090565b6002915061546581611c80565b1490565b6001600160401b036003820154164210159081615484575090565b600180925060ff9101541661546581611c80565b90614a2792916154a66154e4565b91908115614b79576001600160a01b031691826154db576126be92505f808080856001600160a01b0386165af161255a613b68565b6126be92615542565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146155335760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f5114811615615585575b604091909152156153f05750565b6001811516615421573d15833b15151616615577565b905f52600260205260405f2054159081614b96575090565b6001600160401b03815116906020810151600a811015611c8a5761565a8260406156ba9401516155fa60806060840151930151946040519760208901526040880190611c99565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526107f76102408261268e565b9190915f52600260205260405f20918255600582019261570b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611c8a5760c06157c19361572f60029761577794614f49565b6040810151600687015561574a606082015160078801614f76565b61575b6080820151600c8801614f76565b61576c60a082015160118801615063565b015160128501615063565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126157cf5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b8181106158105750506126be9250038361268e565b84548352600194850194879450602090930192016157fb565b6001600160a01b0390613f6b61584f61584a60209895999697993690612ef3565b6155b3565b936040519889978896879563600109bb60e01b875260048701613ee8565b6001810190825f528160205260405f2054155f146158d5578054680100000000000000008110156107ad576158c26158ac826001879401855584613091565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615903575f1901906158f28282613091565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f146159b5575f198401848111611b225783545f19810194908511611b22575f958583615972976159659503615978575b5050506158dc565b905f5260205260405f2090565b55600190565b61599e6159989161598f61059d6159ac9588613091565b92839187613091565b906130a6565b85905f5260205260405f2090565b555f808061595d565b505050505f90565b60405163a9059cbb60e01b602082019081526001600160a01b03939093166024820152604480820194909452928352915f9182916159fc60648261268e565b51908285620186a0f190615a0e613b68565b9115615a4b578151908115615a4257506020811015615a2d5750505f90565b81602091810103126102855760200151151590565b9150503b151590565b50505f90565b9190915f52600560205260405f20918255600582019261570b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b6126be90615aeb615ae594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615a91565b90615a91565b03601f19810184528361268e565b6107f791615b0691615b37565b90929192615b71565b90615b1982612e87565b615b26604051918261268e565b8281528092613244601f1991612e87565b8151919060418303615b6757615b609250602082015190606060408401519301515f1a90615bed565b9192909190565b50505f9160029190565b615b7a81611c80565b80615b83575050565b615b8c81611c80565b60018103615ba35763f645eedf60e01b5f5260045ffd5b615bac81611c80565b60028103615bc7575063fce698f760e01b5f5260045260245ffd5b80615bd3600392611c80565b14615bdb5750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615c64579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610499575f516001600160a01b03811615615c5a57905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea264697066735822122031a0d5c80492f37031544dc36300232be26e9efe9e84abb664e6e5215d179ed664736f6c634300081e0033000000000000000000000000ce87fd88f4b5fd5475d163e2642c5c2c7dd655ec0000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0x4", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x4e378a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x7261a8f5f722c829e8d4381a519c2aa8c8fe689a7d5e1902494d7a74bd68426a", + "transactionIndex": "0x10", + "blockHash": "0x355c108a56e3de80a6f35b078eda90c14789c4f3c575da5669cc44a54cf7fc05", + "blockNumber": "0x2694b98", + "gasUsed": "0x1256bf", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x64980", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "daFootprintGasScalar": "0x94", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x28c91a75", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x1a367c71689", + "l1GasPrice": "0x294eb5f4f", + "l1GasUsed": "0xae0c" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x59581a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x93cf089b7e57207fad34690b6525d97b78d4452eadddc3dd979b28224528b89a", + "transactionIndex": "0x11", + "blockHash": "0x355c108a56e3de80a6f35b078eda90c14789c4f3c575da5669cc44a54cf7fc05", + "blockNumber": "0x2694b98", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x45728", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "daFootprintGasScalar": "0x94", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x28c91a75", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x121849a466a", + "l1GasPrice": "0x294eb5f4f", + "l1GasUsed": "0x7825" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x642214", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x7bb8823f66bdb3f9bef019607214000a7dc0eb0148a523dbce2c6709d42e7a74", + "transactionIndex": "0x12", + "blockHash": "0x355c108a56e3de80a6f35b078eda90c14789c4f3c575da5669cc44a54cf7fc05", + "blockNumber": "0x2694b98", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x42bcc", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "daFootprintGasScalar": "0x94", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x28c91a75", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x1162dfa6dda", + "l1GasPrice": "0x294eb5f4f", + "l1GasUsed": "0x7371" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x6a8455", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x3a7095c50cafe072b66d893f4583722b03a14aba4cc8d9a3b359b88135028f5c", + "transactionIndex": "0x13", + "blockHash": "0x355c108a56e3de80a6f35b078eda90c14789c4f3c575da5669cc44a54cf7fc05", + "blockNumber": "0x2694b98", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x2b2dc", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xce87fd88f4b5fd5475d163e2642c5c2c7dd655ec", + "daFootprintGasScalar": "0x94", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x28c91a75", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0xb40a26c8e7", + "l1GasPrice": "0x294eb5f4f", + "l1GasUsed": "0x4ab6" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xba07ad", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x470639b186a290226a188b8ee03cd26ddf38aedfdc2d09e269284f7cefdc2fcc", + "transactionIndex": "0x14", + "blockHash": "0x355c108a56e3de80a6f35b078eda90c14789c4f3c575da5669cc44a54cf7fc05", + "blockNumber": "0x2694b98", + "gasUsed": "0x4f8358", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x1ed930", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x6e2c4707da119425df2c722e2695300154652f56", + "daFootprintGasScalar": "0x94", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x28c91a75", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x8094bff6956", + "l1GasPrice": "0x294eb5f4f", + "l1GasUsed": "0x355c1" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x353A207A7bC822D8d3E58bcA2f3F9E2b90B26e78", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xb5c2e8BAD7417E654E9087753EC1D08762a06f91", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xaf9F833141094083B9de2cb6AA0f7E1f2d2DEee1" + ], + "pending": [], + "returns": {}, + "timestamp": 1776678417339, + "chain": 84532, + "commit": "6c0a41d5" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779195327198.json b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779195327198.json new file mode 100644 index 000000000..4c0a22a31 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779195327198.json @@ -0,0 +1,229 @@ +{ + "transactions": [ + { + "hash": "0x771c63d6e24076d17e9b68098bd8e0c0cc6616640e83f41dfbbb6aeaccab36cb", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0xa023c476e02786356b84eda6ad6f8c003960f406", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea26469706673582212209929d28d6f4f84686b60107c75de0bf20707b30e60c2aea4ba079dccf78da9ab64736f6c634300081e0033", + "nonce": "0xa", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa04b316adbed5ebb8b0a22369bb4651bb5da836e63b8c6228668ffea1c7fb814", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0x899a6059e7824b7c5538dc7b3a2f5286013ed9d6", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e82", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea2646970667358221220854ea3b8ac791e9ce26e6783173cfd98de9d6f65d7e284de0500fdc6022468f264736f6c634300081e0033", + "nonce": "0xb", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x51faa27c3a73618be9108ec71097d5b8b8f0881fa9bb8c5687d3f468b71449fa", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0x9f9513e2ea0ca353f2108552f8d6a2357c103d20", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea2646970667358221220e4948e1e731e3da271666ebc3a21498d041ba70ab4b8811a088372c3360fe38364736f6c634300081e0033", + "nonce": "0xc", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6569b61c32d2b85b249a9bb4d221d9312de3691351117c62598df3cb222863c8", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb5e7d2b8db56a173ca8c05cddcc1379852cdc095", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea264697066735822122038c13cfc3dcd57a2af2b26923fa6b959f5116c33924710878a46f8a3dc1c3a3164736f6c634300081e0033", + "nonce": "0xd", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xdea921906e12c1c0b319630486a68d96bff9f5b2b35486d5bd1b2f9d06e5f4e2", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xeddf27e378a8b102a98a4b03a3730ef585bfaff5", + "function": null, + "arguments": [ + "0xB5E7D2B8DB56A173Ca8c05CDdCC1379852CdC095", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x686f0b", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b0381739f9513e2ea0ca353f2108552f8d6a2357c103d205af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b0381739f9513e2ea0ca353f2108552f8d6a2357c103d205af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173899a6059e7824b7c5538dc7b3a2f5286013ed9d65af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173899a6059e7824b7c5538dc7b3a2f5286013ed9d65af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b038173a023c476e02786356b84eda6ad6f8c003960f4065af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173899a6059e7824b7c5538dc7b3a2f5286013ed9d65af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b0381739f9513e2ea0ca353f2108552f8d6a2357c103d205af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b038173a023c476e02786356b84eda6ad6f8c003960f4065af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209734d5ca5df677a1bc2d6152129b8d22b047a6c8e88c7f12be97983785566ef764736f6c634300081e0033000000000000000000000000b5e7d2b8db56a173ca8c05cddcc1379852cdc0950000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0xe", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x392f17", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x771c63d6e24076d17e9b68098bd8e0c0cc6616640e83f41dfbbb6aeaccab36cb", + "transactionIndex": "0x12", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27c7f6f", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x12f240", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "daFootprintGasScalar": "0x1be", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x925b3", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x3648512", + "l1GasPrice": "0x966542", + "l1GasUsed": "0xae0c" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x51c63c", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x51faa27c3a73618be9108ec71097d5b8b8f0881fa9bb8c5687d3f468b71449fa", + "transactionIndex": "0x15", + "blockHash": "0x3b2886405b7fb83d3032dc998b23db40c0908f0f348d24ac37d11a2b15e806b3", + "blockNumber": "0x27c7f6f", + "gasUsed": "0xb2084", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xd147c", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "daFootprintGasScalar": "0x1be", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x925b3", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x4138040f", + "l1GasPrice": "0x966542", + "l1GasUsed": "0x7825" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x5c9036", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6569b61c32d2b85b249a9bb4d221d9312de3691351117c62598df3cb222863c8", + "transactionIndex": "0x16", + "blockHash": "0x3b2886405b7fb83d3032dc998b23db40c0908f0f348d24ac37d11a2b15e806b3", + "blockNumber": "0x27c7f6f", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xc91d2", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "daFootprintGasScalar": "0x1be", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x925b3", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x3eaa2695", + "l1GasPrice": "0x966542", + "l1GasUsed": "0x7371" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x62f277", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xa04b316adbed5ebb8b0a22369bb4651bb5da836e63b8c6228668ffea1c7fb814", + "transactionIndex": "0x17", + "blockHash": "0x3b2886405b7fb83d3032dc998b23db40c0908f0f348d24ac37d11a2b15e806b3", + "blockNumber": "0x27c7f6f", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x821ea", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xb5e7d2b8db56a173ca8c05cddcc1379852cdc095", + "daFootprintGasScalar": "0x1be", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x925b3", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x288e95ab", + "l1GasPrice": "0x966542", + "l1GasUsed": "0x4ab6" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xb347e2", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xdea921906e12c1c0b319630486a68d96bff9f5b2b35486d5bd1b2f9d06e5f4e2", + "transactionIndex": "0x18", + "blockHash": "0x3b2886405b7fb83d3032dc998b23db40c0908f0f348d24ac37d11a2b15e806b3", + "blockNumber": "0x27c7f6f", + "gasUsed": "0x50556b", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x5e50fa", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xeddf27e378a8b102a98a4b03a3730ef585bfaff5", + "daFootprintGasScalar": "0x1be", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x925b3", + "l1BlobBaseFeeScalar": "0xa118b", + "l1Fee": "0x1d631f789", + "l1GasPrice": "0x966542", + "l1GasUsed": "0x36233" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0xa023C476E02786356b84EdA6ad6f8C003960F406", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x9F9513E2Ea0cA353f2108552F8d6A2357C103d20", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x899A6059E7824b7C5538DC7B3a2F5286013ED9D6" + ], + "pending": [], + "returns": {}, + "timestamp": 1779195327198, + "chain": 84532, + "commit": "df4e110a" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779278728832.json b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779278728832.json new file mode 100644 index 000000000..4ddad5054 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779278728832.json @@ -0,0 +1,188 @@ +{ + "transactions": [ + { + "hash": "0xd382762780c6e0661cc1f7bb228f30f6e46461be9efacc3b54750dbe8d27dfa0", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1ad222", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576114b4908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b60403660031901126110285760043567ffffffffffffffff81116110285760a060031982360301126110285761005a82611093565b80600401356006811015611028578252602481013567ffffffffffffffff81116110285761008e90600436918401016111f6565b602083019081526040830192604483013584526100b8608460608301946064810135865201611101565b6080820190815260243567ffffffffffffffff8111611028576100df9036906004016111f6565b926100e86112ad565b50606084019367ffffffffffffffff855151164603610c5b5767ffffffffffffffff81511682519067ffffffffffffffff82511610908115611083575b50156108d357815161013681611324565b15611043575b5084516040810190601260ff83511611611034574667ffffffffffffffff82511614610f5e575b505060208101928351600a81101561034c576004148015610f4a575b8015610f36575b8015610f22575b8015610f0e575b8015610efa575b15610ed6576080820167ffffffffffffffff8151511615610ec757515167ffffffffffffffff164614610eb8575b6101de865160a06060820151910151906112e4565b6101f3875160c06080820151910151906112f1565b5f8112610ea95761020390611354565b03610e9a578451600681101561034c57600214610e74575b506102246112ad565b5061023e608086510151608060608551015101519061130c565b9161025860c08751015160c060608451015101519061130c565b9351600a81101561034c576002810361042c5750505090916102786112ad565b908051600681101561034c5715908115610416575b8115610400575b81156103eb575b50156103dc575f8213156103cd576102d5926102cd9282526020820152600160408201525f6060820152945b516113a0565b8451906112f1565b916102e660208501938451906112f1565b5f81126103be576080850192835115610385575b50508251905f8213610360575b50506040519183518352516020830152604083015192600684101561034c57606067ffffffffffffffff9160a095604086015201511660608301525115156080820152f35b634e487b7160e01b5f52602160045260245ffd5b61036b905191611354565b11610377575f80610307565b62b8ec7b60e61b5f5260045ffd5b61039d6103a3915160a06060820151910151906112e4565b91611354565b036103af575f806102fa565b6347c801f760e11b5f5260045ffd5b63ae0bb49160e01b5f5260045ffd5b631180da8f60e01b5f5260045ffd5b631e40ad6360e31b5f5260045ffd5b905051600681101561034c576004145f61029b565b80915051600681101561034c5760021490610294565b80915051600681101561034c576001149061028d565b600381036104d55750505090916104416112ad565b908051600681101561034c57159081156104bf575b81156104a9575b8115610494575b50156103dc575f8212156103cd576102d5926102cd9282526020820152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610464565b80915051600681101561034c576002149061045d565b80915051600681101561034c5760011490610456565b8061058c575050506104e56112ad565b928051600681101561034c5715908115610576575b8115610560575b811561054b575b50156103dc576103cd5760a08351015161053c57816102cd9160206102d5940152600160408201525f6060820152946102c7565b63a5eabfa560e01b5f5260045ffd5b905051600681101561034c576004145f610508565b80915051600681101561034c5760021490610501565b80915051600681101561034c57600114906104fa565b939492936001810361065b575050506105a36112ad565b908051600681101561034c57600114908115610645575b8115610630575b50156103dc576060845101516106215760a08451015161053c575f6105f3846105ee856105ee8b516113a0565b6112f1565b126103be576102d5926102cd9282526020820152600360408201525f606082015260016080820152946102c7565b631605013b60e11b5f5260045ffd5b905051600681101561034c576004145f6105c1565b80915051600681101561034c57600214906105ba565b9294939192600481036107475750506106726112ad565b938051600681101561034c57600114908115610731575b811561071c575b50156103dc576103cd57608001606081510151908115610621576106c2855160ff604060a083015192015116906113fe565b6106d460ff60408451015116846113fe565b0361053c5760806106e891510151916113a0565b0361070d57816102cd9160206102d5940152600160408201525f6060820152946102c7565b634c66f95560e01b5f5260045ffd5b905051600681101561034c576004145f610690565b80915051600681101561034c5760021490610689565b90939192906005810361091c575061075d6112ad565b948051600681101561034c57600114908115610906575b81156108f1575b50156103dc5761078e60208551016112d7565b600a81101561034c576004036108e25767ffffffffffffffff81511667ffffffffffffffff6107c08187515116611370565b16036108d357608001916060835101516106215760a08351015161053c5760a08651015161053c576103cd576108c457606060808351015101519060808151015161080a836113a0565b0361070d575160c00151610825610820836113a0565b611390565b036108b5576060845101519060608084510151015182039182116108a15760ff6040608061085d61086a9584848b51015116906113fe565b95510151015116906113fe565b03610892576102cd815f6102d593525f6020820152600160408201525f6060820152946102c7565b63733d14c560e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b630c18740d60e01b5f5260045ffd5b636c8b750760e11b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b637dcd8ffd60e01b5f5260045ffd5b905051600681101561034c576004145f61077b565b80915051600681101561034c5760021490610774565b91939091600681036109e0575050906109336112ad565b938051600681101561034c576001149081156109ca575b81156109b5575b50156103dc576103cd5760a08451015161053c5760800160808151015161070d576060815101516106215760c061098c60a0835101516113a0565b91510151036108b557816102cd9160206102d5940152600160408201525f6060820152946102c7565b905051600681101561034c576004145f610951565b80915051600681101561034c576002149061094a565b60078103610a7a575050906109f36112ad565b938051600681101561034c57600114908115610a64575b8115610a4f575b50156103dc576103cd5760a08451015161053c576080016060815101516106215760a08151015161053c57516106e860c06080830151920151611390565b905051600681101561034c576004145f610a11565b80915051600681101561034c5760021490610a0a565b60088103610c9357505090610a8d6112ad565b938051600681101561034c57158015610c7f575b15610b74575050608001805160600151915081156106215760a08151015161053c5760608451015161062157610ae5845160ff604060a083015192015116906113fe565b610af760ff60408451015116846113fe565b03610b6557610b2e9060ff6040610b23610b1d8851848460c0830151920151169061143b565b956113a0565b92510151169061143b565b036108b55760808251015161070d576102cd6102d591610b5260a0855101516113a0565b6020820152600460408201525b946102c7565b637b208b9d60e01b5f5260045ffd5b8051600681101561034c57600114908115610c6a575b50156103dc574667ffffffffffffffff8651511603610c5b576103cd576060845101519081156106215760a08551015161053c576080019060608251015161062157610be4825160ff604060a083015192015116906113fe565b610bf660ff60408851015116836113fe565b03610b6557610c2e610c1f610c19845160ff604060c0830151920151169061143b565b926113a0565b60ff604088510151169061143b565b036108b557516080015161070d57816102cd9160206102d5940152600160408201525f6060820152610b5f565b636752558360e01b5f5260045ffd5b905051600681101561034c576002145f610b8a565b508051600681101561034c57600514610aa1565b600903610e6557610ca26112ad565b948051600681101561034c57600403610d5f57504667ffffffffffffffff8751511603610c5b57610cd660208251016112d7565b600a81101561034c576008036108e25767ffffffffffffffff82511667ffffffffffffffff610d088184515116611370565b16036108d35760806060915101510151606086510151036106215760a08551015161053c57608001606081510151610621575160a0015161053c576103cd576108c4576102cd81600160406102d5940152946102c7565b809192935051600681101561034c57600114908115610e50575b50156103dc574667ffffffffffffffff8651511603610c5b57610d9f60208251016112d7565b600a81101561034c57600803610e215767ffffffffffffffff610dc9818085511693515116611370565b16036108d3575b6060845101516106215760a08451015161053c5760800160608151015115610621575160a0015161053c57816102cd9160206102d5940152600560408201525f606082015260016080820152610b5f565b67ffffffffffffffff610e3b818085511693515116611370565b1610610dd0576307646e4960e01b5f5260045ffd5b905051600681101561034c576002145f610d79565b633226144f60e21b5f5260045ffd5b5167ffffffffffffffff164211610e8b575f61021b565b63f06506c560e01b5f5260045ffd5b63780cef0760e11b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b630452a77d60e21b5f5260045ffd5b6309b086b960e21b5f5260045ffd5b67ffffffffffffffff60808301515116156101c95763263dac3760e11b5f5260045ffd5b508351600a81101561034c5760091461019b565b508351600a81101561034c57600814610194565b508351600a81101561034c5760071461018d565b508351600a81101561034c57600614610186565b508351600a81101561034c5760051461017f565b6020015173ffffffffffffffffffffffffffffffffffffffff1680610fa1575060ff601291511603610f92575b5f80610163565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610ff2575b50610fd657636afa2af960e01b5f5260045ffd5b60ff80915116911614610f8b57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161102c575b8161100e602093836110df565b81010312611028575160ff8116810361102857915f610fc2565b5f80fd5b3d9150611001565b632c05b0fd60e21b5f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff6020606082828a5101511693015101511603611074575f61013c565b63936bb5ad60e01b5f5260045ffd5b61108d9150611324565b5f610125565b60a0810190811067ffffffffffffffff8211176110af57604052565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff8211176110af57604052565b90601f8019910116810190811067ffffffffffffffff8211176110af57604052565b359067ffffffffffffffff8216820361102857565b91908260e09103126110285760405161112e816110c3565b809261113981611101565b8252602081013573ffffffffffffffffffffffffffffffffffffffff81168103611028576020830152604081013560ff811681036110285760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156110285780359067ffffffffffffffff82116110af57604051926111d5601f8401601f1916602001856110df565b8284526020838301011161102857815f926020809301838601378301015290565b9190610260838203126110285760405190611210826110c3565b819361121b81611101565b83526020810135600a811015611028576020840152604081013560408401526112478260608301611116565b606084015261125a826101408301611116565b608084015261022081013567ffffffffffffffff811161102857826112809183016111a0565b60a08401526102408101359167ffffffffffffffff83116110285760c0926112a892016111a0565b910152565b604051906112ba82611093565b5f6080838281528260208201528260408201528260608201520152565b51600a81101561034c5790565b919082018092116108a157565b9190915f83820193841291129080158216911516176108a157565b81810392915f1380158285131691841216176108a157565b67ffffffffffffffff6060820151511615908161133f575090565b67ffffffffffffffff91506080015151161590565b5f811261135e5790565b635467221960e11b5f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116108a157565b600160ff1b81146108a1575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116113ca5790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116108a157565b60ff16604d81116108a157600a0a90565b9060ff811660128111611034576012146114375761141e611423916113dc565b6113ed565b908181029181830414901517156108a15790565b5090565b9060ff811660128111611034576012146114375761141e61145b916113dc565b81810291905f8212600160ff1b8214166108a15781830514901517156108a1579056fea2646970667358221220bc01b237f03208049bfd81bfcbeaaad6cc979f220dd2246c2d2aae7ea34b3db664736f6c634300081e0033", + "nonce": "0x10", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3a642c4c020499885b2b289bfacabbf19b80a4165e7968109c0cc0536753ae7f", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xf5e93", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610c31908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e7146100ff576324063eba1461002e575f80fd5b60203660031901126100fb5760043567ffffffffffffffff81116100fb5761005a9036906004016109ec565b610062610aac565b905160048110156100e7575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610a61565b0390f35b634e487b7160e01b5f52601160045260245ffd5b630725d7e560e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60403660031901126100fb5760043567ffffffffffffffff81116100fb5761012b9036906004016109ec565b60243567ffffffffffffffff81116100fb5761014b903690600401610935565b610153610aac565b50815160048110156100e7576003146107d05767ffffffffffffffff461660608201908067ffffffffffffffff83515116146107c157608083019067ffffffffffffffff82515116036107b25767ffffffffffffffff835116156106785780516040810190601260ff835116116107a3574667ffffffffffffffff825116146106de575b5050805160a0606082015191015181018091116100c457610203825160c0608082015191015190610ad6565b5f81126106cf5761021390610b01565b036106c057835160048110156100e75760021461069b575b610233610aac565b5060208301928351600a8110156100e75760068103610448575050610256610aac565b91845160048110156100e7576104395760608251015161042a5760808251015161041b5781519160c060a084015193015161029084610b1d565b0361040c576102bd60ff60406102b28551838360608301519201511690610b7b565b935101511684610b7b565b116103fd575160a001516103ee576102d490610b1d565b60208201526001604082015260016080820152915b8251158015906103e1575b156103d25782519161030c6020850193845190610ad6565b908051600a8110156100e757600603610369575082510361035a5760806103369101519151610b01565b1161034c576100c0905b60405191829182610a61565b62b8ec7b60e61b5f5260045ffd5b638041118f60e01b5f5260045ffd5b9091925051600a8110156100e75760071461038a575b50506100c090610340565b8251036103c35760406103a56103a08451610af1565b610b01565b910151036103b457818061037f565b631b22645160e31b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b630bde39a760e11b5f5260045ffd5b50602083015115156102f4565b63a5eabfa560e01b5f5260045ffd5b63e19f88d560e01b5f5260045ffd5b63035a66d760e11b5f5260045ffd5b634c66f95560e01b5f5260045ffd5b631605013b60e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b90929060070361065a5761045a610aac565b92855160048110156100e7576001148015610687575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff1603610678576001600160a01b03602084510151166001600160a01b0360206080845101510151160361066957602081510151600a8110156100e7576005190161065a5760a060808251015101519260608151015161042a5760808151015161051e61051986610b1d565b610af1565b0361041b5760a0815101516103ee575160c0015161053b84610b1d565b0361062d57606082510151606080835101510151111561064b5760608082510151015160608351015181039081116100c4576105809060ff6040855101511690610b7b565b61059660ff604060808551015101511685610b7b565b0361063c5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561062d57604060806105f66105f06106039660ff856105e58298610af1565b925101511690610bb8565b96610b1d565b9351015101511690610bb8565b0361040c576106186105196040850151610b1d565b8152600360408201525f6080820152916102e9565b630c18740d60e01b5f5260045ffd5b63ffda345d60e01b5f5260045ffd5b6304bc7c3760e31b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b50855160048110156100e757600214610470565b67ffffffffffffffff60608501511642111561022b5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b031680610714575060ff601291511603610705575b84806101d7565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f9281610765575b5061074957636afa2af960e01b5f5260045ffd5b60ff809151169116146106fe57635a8dbaed60e01b5f5260045ffd5b9092506020813d60201161079b575b816107816020938361082b565b810103126100fb575160ff811681036100fb579187610735565b3d9150610774565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107fb57604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176107fb57604052565b90601f8019910116810190811067ffffffffffffffff8211176107fb57604052565b359067ffffffffffffffff821682036100fb57565b91908260e09103126100fb5760405161087a816107df565b80926108858161084d565b825260208101356001600160a01b03811681036100fb576020830152604081013560ff811681036100fb5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156100fb5780359067ffffffffffffffff82116107fb5760405192610914601f8401601f19166020018561082b565b828452602083830101116100fb57815f926020809301838601378301015290565b9190610260838203126100fb576040519061094f826107df565b819361095a8161084d565b83526020810135600a8110156100fb576020840152604081013560408401526109868260608301610862565b6060840152610999826101408301610862565b608084015261022081013567ffffffffffffffff81116100fb57826109bf9183016108df565b60a08401526102408101359167ffffffffffffffff83116100fb5760c0926109e792016108df565b910152565b91909160a0818403126100fb5760405190610a068261080f565b8193813560048110156100fb57835260208201359167ffffffffffffffff83116100fb57610a3a6080939284938301610935565b602085015260408101356040850152610a556060820161084d565b60608501520135910152565b91909160a08101928051825260208101516020830152604081015160048110156100e7576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610ab98261080f565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b600160ff1b81146100c4575f0390565b5f8112610b0b5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b475790565b63123baf0360e11b5f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff8116601281116107a357601214610bb457610b9b610ba091610b59565b610b6a565b908181029181830414901517156100c45790565b5090565b9060ff8116601281116107a357601214610bb457610b9b610bd891610b59565b81810291905f8212600160ff1b8214166100c45781830514901517156100c4579056fea26469706673582212206962417e2cd8eb647234f143f2014cfb5f1a06887319a0bcf2a01e09331d57de64736f6c634300081e0033", + "nonce": "0x11", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3c6043743ee8f39a6fe2b4b40e236755c2dd7da980d4fc5340e3e44fe4f949e4", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0xfc763", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610bc8908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146106f95763bbc42f341461002f575f80fd5b60403660031901126106b15760043567ffffffffffffffff81116106b15761005b9036906004016109a1565b60243567ffffffffffffffff81116106b15761007b9036906004016108ea565b610083610a89565b508151600481101561030b576003146106ea5767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146106db57608082019067ffffffffffffffff82515116036106cc5767ffffffffffffffff8251161561057f5780516040810190601260ff835116116106bd574667ffffffffffffffff825116146105f4575b5050805160a06060820151910151810180911161033b57610134825160c0608082015191015190610aca565b5f81126105e55761014490610af5565b036105d6578351600481101561030b576002146105b1575b610164610a89565b5060208201928351600a81101561030b576004810361039a5750909150610189610a89565b918451600481101561030b5761038b57805191608060608401519301516101af84610b11565b0361037c5760a08251015161036d5760c08251015161035e5760ff60406101e56101f09351838360a08301519201511690610b4d565b935101511683610b4d565b0361034f576101fe90610b11565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161033b5767ffffffffffffffff166060820152600160a0820152915b82511580159061032e575b1561031f57825161025f6020850191825190610aca565b928051600a81101561030b576004036102a0575050508151036102915761028d905b60405191829182610a2a565b0390f35b638041118f60e01b5f5260045ffd5b9290919251600a81101561030b576005146102c2575b50505061028d90610281565b8151036102fc576102dd6102d860409251610ae5565b610af5565b910151036102ed575f80806102b6565b63b09443e760e01b5f5260045ffd5b6322e0e03d60e21b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b630bde39a760e11b5f5260045ffd5b5060208301511515610248565b634e487b7160e01b5f52601160045260245ffd5b63e19f88d560e01b5f5260045ffd5b630c18740d60e01b5f5260045ffd5b63a5eabfa560e01b5f5260045ffd5b633b5613e560e11b5f5260045ffd5b63ed77877960e01b5f5260045ffd5b600503610561576103a9610a89565b928551600481101561030b57600114801561059d575b1561058e5767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161033b5767ffffffffffffffff160361057f576001600160a01b03602082510151166001600160a01b0360206080865101510151160361057057602083510151600a81101561030b576003190161056157606060808451015101519060808151015161045c83610b11565b0361037c5760c08151015161047861047384610b11565b610ae5565b0361035e57606081510151610552575160a0015161036d57606082510151606080855101510151810390811161033b576104be6104d29160ff6040865101511690610b4d565b9160ff604060808751015101511690610b4d565b036105435760a08151015161036d57606060808092510151925101510151908181035f831282808312821692139015161761033b57036105345761051c6104736040850151610b11565b6020820152600360408201525f60a08201529161023d565b631180da8f60e01b5f5260045ffd5b630ff0edb360e41b5f5260045ffd5b631605013b60e11b5f5260045ffd5b633226144f60e21b5f5260045ffd5b630486bbc360e51b5f5260045ffd5b6307646e4960e01b5f5260045ffd5b630725d7e560e31b5f5260045ffd5b508551600481101561030b576002146103bf565b67ffffffffffffffff60808501511642111561015c5763f06506c560e01b5f5260045ffd5b6392ad5c7560e01b5f5260045ffd5b630e6af40760e41b5f5260045ffd5b602001516001600160a01b03168061062a575060ff60129151160361061b575b5f80610108565b635a8dbaed60e01b5f5260045ffd5b9060206004926040519384809263313ce56760e01b82525afa5f928161067b575b5061065f57636afa2af960e01b5f5260045ffd5b60ff8091511691161461061457635a8dbaed60e01b5f5260045ffd5b9092506020813d6020116106b5575b81610697602093836107e0565b810103126106b1575160ff811681036106b157915f61064b565b5f80fd5b3d915061068a565b632c05b0fd60e21b5f5260045ffd5b639ba78e5560e01b5f5260045ffd5b6321e65f6560e01b5f5260045ffd5b636915564560e01b5f5260045ffd5b60203660031901126106b15760043567ffffffffffffffff81116106b1576107259036906004016109a1565b61072d610a89565b908051600481101561030b575f190161058e576060015167ffffffffffffffff164210156107a157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161033b5767ffffffffffffffff61028d921660808201525f60a082015260405191829182610a2a565b63159ce82160e11b5f5260045ffd5b60e0810190811067ffffffffffffffff8211176107cc57604052565b634e487b7160e01b5f52604160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176107cc57604052565b359067ffffffffffffffff821682036106b157565b91908260e09103126106b15760405161082f816107b0565b809261083a81610802565b825260208101356001600160a01b03811681036106b1576020830152604081013560ff811681036106b15760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156106b15780359067ffffffffffffffff82116107cc57604051926108c9601f8401601f1916602001856107e0565b828452602083830101116106b157815f926020809301838601378301015290565b9190610260838203126106b15760405190610904826107b0565b819361090f81610802565b83526020810135600a8110156106b15760208401526040810135604084015261093b8260608301610817565b606084015261094e826101408301610817565b608084015261022081013567ffffffffffffffff81116106b15782610974918301610894565b60a08401526102408101359167ffffffffffffffff83116106b15760c09261099c9201610894565b910152565b91909160a0818403126106b1576040519060a0820182811067ffffffffffffffff8211176107cc576040528193813560048110156106b157835260208201359067ffffffffffffffff82116106b15782610a046080949261099c948694016108ea565b602086015260408101356040860152610a1f60608201610802565b606086015201610802565b91909160c081019280518252602081015160208301526040810151600481101561030b5760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b6040519060c0820182811067ffffffffffffffff8211176107cc576040525f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761033b57565b600160ff1b811461033b575f0390565b5f8112610aff5790565b635467221960e11b5f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610b3b5790565b63123baf0360e11b5f5260045260245ffd5b9060ff16601281116106bd5760128114610b8e5760120360ff811161033b5760ff16604d811161033b57600a0a9081810291818304149015171561033b5790565b509056fea26469706673582212207eb21c0d1619a3248a4b3fe08eef8df6e3a6e3feca56e1a09ecf8241267f767564736f6c634300081e0033", + "nonce": "0x12", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xb63e91129e4742f33a2e81449b74ddbe783a3c87188f4b2a73062089cae9a37d", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x61b9e0767f2eca7e33802e82f9c64b1ebe72ba31", + "function": null, + "arguments": [ + "0xB5E7D2B8DB56A173Ca8c05CDdCC1379852CdC095", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "gas": "0x68906c", + "value": "0x0", + "input": "0x60c03461010b57601f615f0038819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615ddc908161012482396080518181816111960152613ef1015260a051818181610c6101528181610d7e0152818161145501528181611a5e0152818161209d0152818161363d0152818161409e01528181614667015261476f0152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461028457806316b390b11461027f578063187576d81461027a5780633115f6301461027557806338a66be2146102705780633c684f921461026b57806341b660ef1461026657806347de477a1461026157806351bfcdbd1461025c57806353269198146102575780635a0745b4146102525780635ae2accc1461024d5780635b9acbf9146102485780635dc46a74146102435780636840dbd21461023e5780636898234b1461023957806371a4714114610234578063735181f01461022f57806382d3e15d1461022a5780638d0b12a5146102255780638e31c73514610220578063941910511461021b5780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ce565b6126b7565b612598565b61251d565b61247f565b612305565b61214e565b612032565b611f29565b611c9a565b611c1a565b611bfd565b611b0e565b611790565b611631565b611616565b611509565b6114ec565b611389565b611242565b611225565b6111df565b611177565b611098565b611081565b611036565b611000565b610fe5565b610fc9565b610dd1565b610d5f565b610b9b565b610875565b6107b2565b610777565b610580565b6104fa565b610356565b61029e565b6001600160a01b0381160361029a57565b5f80fd5b3461029a57602036600319011261029a576001600160a01b036004356102c381610289565b165f526006602052602060405f2054604051908152f35b9181601f8401121561029a578235916001600160401b03831161029a576020838186019501011161029a57565b60643590600282101561029a57565b90606060031983011261029a5760043591602435906001600160401b03821161029a57610345916004016102da565b9091604435600281101561029a5790565b3461029a576103b86103f261036a36610316565b9294916103cd610385879693965f52600260205260405f2090565b948554926103948415156126e9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613ed0565b9192909901986103c78a612903565b87614001565b60c06103d8876140f3565b604051809481926301999b9360e61b835260048301612a73565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104ae577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610466946080945f9361047b575b5082610458939461045189612903565b908b614167565b01516001600160401b031690565b9061047660405192839283612bae565b0390a2005b61045893506104a19060c03d60c0116104a7575b610499818361277f565b8101906129b1565b92610441565b503d61048f565b612a84565b90602080835192838152019201905f5b8181106104d05750505090565b82518452602093840193909201916001016104c3565b9060206104f79281815201906104b3565b90565b3461029a57602036600319011261029a576001600160a01b0360043561051f81610289565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056a576105668561055a8187038261277f565b604051918291826104e6565b0390f35b8254845260209093019260019283019201610543565b3461029a57602036600319011261029a57600354600480545f929183903582841115610771576105b0838561334c565b8082101561076357506105c781959493929561330d565b925b8083108061075a575b1561074d576105ed6105e384613165565b90549060031b1c90565b610608610602825f52600260205260405f2090565b966139d6565b9561061281615577565b6107385761061f816155a7565b156106e8576001600160a01b036106d0610602600198999a6106b0955f866106bf610666600c5f516020615d875f395f51905f529a01546001600160a01b039060401c1690565b9d8e92610684846001600160a01b03165f52600660205260405f2090565b5493610696600483019586549061333f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106ca828d613359565b526139d6565b604051938452961691602090a25b94939291946105c9565b505050506106f891939250600455565b806106ff57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261073360405192839283614480565b0390a1005b505092939491610747906139d6565b926106de565b50506004559190506106f8565b508185106105d2565b6105c790959493929561330d565b5f6105b0565b3461029a575f36600319011261029a576020604051620186a08152f35b6004359060ff8216820361029a57565b359060ff8216820361029a57565b3461029a57602036600319011261029a5760ff6107cd610794565b165f52600760205260405f2060405160408101918183106001600160401b0384111761082b576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126fe565b908161026091031261029a5790565b90600319820160e0811261029a5760c01361029a5760049160c435906001600160401b03821161029a576104f791600401610830565b61087e3661083f565b60208101600261088d82612bdf565b61089681611d88565b148015610b80575b8015610b62575b6108ae90612be9565b60026108b982612bdf565b6108c281611d88565b03610b53575b6109a86109066108d83686612c2e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261093461092561091e865f525f60205260405f2090565b5460ff1690565b61092e816123db565b15612ca2565b61094060208601612cb8565b9061094a8661462c565b61095a608087013583838861472d565b60a08161098d61098661096f60808401612cb8565b6001600160a01b03165f52600660205260405f2090565b5488614794565b604051632a2d120f60e21b8152958692839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104ae577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a1d610a9e936001600160a01b03965f91610b24575b50610a0c368b612c2e565b610a163686612fe4565b908a6148e0565b610a4187610a3c866001600160a01b03165f52600160205260405f2090565b6159ab565b506002610a4d82612bdf565b610a5681611d88565b03610aa35750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a8c8582613090565b0390a25b6040519384931696836130a1565b0390a3005b610aae600391612bdf565b610ab781611d88565b03610af457857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610aec8582613090565b0390a2610a90565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610aec8582613090565b610b46915060a03d60a011610b4c575b610b3e818361277f565b810190612cc2565b5f610a01565b503d610b34565b610b5d3415612bff565b6108c8565b506108ae610b6f82612bdf565b610b7881611d88565b1590506108a5565b506003610b8c82612bdf565b610b9581611d88565b1461089e565b610ba43661083f565b90610bc56004610bb660208501612bdf565b610bbf81611d88565b14612be9565b610bce8161462c565b610bdb6108d83683612c2e565b916080610bea60208401612cb8565b92013591610bfa8382848761472d565b610c1e610c0683613130565b85906001600160401b03915f521660205260405f2090565b92610c28856149f3565b15610ca8575050610a9e7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c886001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613202565b610c923415612bff565b610c9c8186614a4f565b60405191829182613090565b9091610cd460c082610cb9876140f3565b604051632ef10bcd60e21b815293849283926004840161313a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104ae577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9e94610d37935f91610d40575b50610d303686612fe4565b8989614167565b610c9c846131b4565b610d59915060c03d60c0116104a757610499818361277f565b5f610d25565b3461029a575f36600319011261029a5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b90604060031983011261029a5760043591602435906001600160401b03821161029a576104f791600401610830565b3461029a57610ddf36610da2565b610df06009610bb660208401612bdf565b610e0c6001610e06845f525f60205260405f2090565b01613218565b610ea7610e2360208301516001600160a01b031690565b91610e34608082015184868861472d565b610e3e3685612fe4565b61014085019386610e4e86613130565b6001600160401b031646149586610f61575b50505060a081610e8c610e8561096f60206060850151016001600160a01b0390511690565b5489614794565b604051632a2d120f60e21b81529586928392600484016132a2565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae57610ed9935f93610f40575b50866148e0565b15610f0f576104767f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613090565b6104767f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613090565b610f5a91935060a03d60a011610b4c57610b3e818361277f565b915f610ed2565b610fc092610f73610fbb923690612f05565b6060860152610f853660608b01612f05565b6080860152610f9261328e565b60a0860152610f9f61328e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a55565b505f8681610e60565b3461029a575f36600319011261029a576020604051612a308152f35b3461029a575f36600319011261029a57602060405160408152f35b3461029a57604036600319011261029a5761056661102260243560043561336d565b6040519182916020835260208301906104b3565b3461029a5761104d61104736610da2565b90613426565b005b606060031982011261029a576004359160243591604435906001600160401b03821161029a576104f791600401610830565b3461029a5761104d6110923661104f565b91613776565b3461029a57602036600319011261029a576001600160a01b036004356110bd81610289565b165f5260016020526110d160405f2061591f565b5f905f5b8151811015611164576110fc61091e6110ee8385613359565b515f525f60205260405f2090565b611105816123db565b6003811415908161114f575b5061111f575b6001016110d5565b9161113281846001931061113a576139d6565b929050611117565b6111448585613359565b516106ca8286613359565b6005915061115c816123db565b14155f611111565b50610566918152604051918291826104e6565b3461029a575f36600319011261029a5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b604090600319011261029a576004356111d281610289565b906024356104f781610289565b3461029a57602061121c6001600160a01b036111fa366111ba565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b3461029a575f36600319011261029a576020600454604051908152f35b3461029a5761125036610316565b61129c611268859493945f52600560205260405f2090565b918254946112778615156126e9565b60a061128288614c8f565b604051809581926312031f5d60e11b8352600483016139e4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104ae577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c796610466966060965f95611346575b50916113368596610458969385600561131a600161132a9901546001600160a01b039060081c1690565b97889360028401549a8b91613ed0565b92909193019e8f612903565b61133f89612903565b908b614d49565b610458955061132a939192966113766113369260a03d60a011611382575b61136e818361277f565b8101906136c5565b965096929193506112f0565b503d611364565b3461029a57606036600319011261029a576113a2610794565b6024356113ae81610289565b6044356001600160401b03811161029a576114c1916113d46114c69236906004016102da565b93909461148761148260ff8316966113ed8815156139f5565b6001600160a01b038616986114038a1515613a0b565b6114448561143e6114326114326114258460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a21565b61147c6114528b8730614e80565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f93565b90614eb8565b613a3f565b6114a16114926127a0565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a55565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b3461029a575f36600319011261029a576020604051620151808152f35b3461029a5761159261151a36610da2565b61153b61152c60208395949501612bdf565b61153581611d88565b15612be9565b6115516001610e06855f525f60205260405f2090565b9061157661156960208401516001600160a01b031690565b608084015190838761472d565b60a08161098d61158b61096f60808401612cb8565b5487614794565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047693610c9c925f926115f5575b506115ee3685612fe4565b90876148e0565b61160f91925060a03d60a011610b4c57610b3e818361277f565b905f6115e3565b3461029a575f36600319011261029a576020604051603c8152f35b3461029a5761163f3661083f565b906116516006610bb660208501612bdf565b61165a8161462c565b6116676108d83683612c2e565b91608061167660208401612cb8565b920135916116868382848761472d565b611692610c0683613130565b9261169c856149f3565b156116d2575050610a9e81610c9c7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a4f565b909161170e60a0826116f46116ed61096f6101608401612cb8565b5488614cec565b60405162ea54e760e01b815293849283926004840161375f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104ae577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9e94610c9c935f91611771575b5061176a3686612fe4565b8989614d49565b61178a915060a03d60a0116113825761136e818361277f565b5f61175f565b608036600319011261029a576004356024356001600160401b03811161029a576117be903690600401610830565b6044356001600160401b03811161029a576117dd9036906004016102da565b90916117e7610307565b926117f9855f525f60205260405f2090565b61180560018201613218565b93611811825460ff1690565b9061181b826123db565b6001821495868015611afb575b61183190612ca2565b61183d60058501612903565b9261187b61184a88613130565b6001600160401b0361187261186688516001600160401b031690565b6001600160401b031690565b91161015613ac3565b60208201516001600160a01b0316978a6080840151956001600160401b036118b66118666118a88d613130565b93516001600160401b031690565b91161115611aad575061190b61194d9493926004926118f660208c01926118f160016118e186612bdf565b6118ea81611d88565b1415612be9565b6123db565b80611a8d575b6119069015612be9565b612bdf565b61191481611d88565b1480611a5a575b6119259015613202565b6119318489898d61472d565b60a08761098d61194661096f60808401612cb8565b548d614794565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119db8d8b6119cf611a0e9a6119e197611a2c9e6119ca6119f69c6119ff9e5f91611a3b575b506119c33688612fe4565b8d896152e2565b613ed0565b93919490923690612fe4565b90614001565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613af7565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047660405192839283613b17565b611a54915060a03d60a011610b4c57610b3e818361277f565b5f6119b8565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316141561191b565b506119066009611a9c83612bdf565b611aa581611d88565b1490506118fc565b6119f69392506119e19150996014996119db7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119cf611a0e9a6119ff9a611a2c9e6119ca3415612bff565b50611b05836123db565b60048314611828565b604036600319011261029a57600435611b2681610289565b6001600160a01b0360243591611b3d831515613b37565b611b45615622565b611b508382336154b6565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bf8575f516020615d875f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611be561047694835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132c7565b3461029a575f36600319011261029a57602060405162093a808152f35b3461029a57611c3f611c2b36610da2565b61153b6003610bb660208496959601612bdf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047693610c9c925f926115f557506115ee3685612fe4565b3461029a575f36600319011261029a57600354600454905f805b82841015611d5c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cf783615577565b611d4a57611d04836155a7565b15611d3357611d2a916004611d1b611d24936139d6565b9401549061333f565b936139d6565b915b9192611cb4565b92509250505b604080519182526020820192909252f35b915092611d56906139d6565b91611d2c565b92509050611d39565b634e487b7160e01b5f52602160045260245ffd5b60041115611d8357565b611d65565b600a1115611d8357565b90600a821015611d835752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f7916001600160401b038251168152611de660208301516020830190611d92565b60408201516040820152611e536060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611ed460a0840151610260610220850152610260840190611d9f565b92015190610240818403910152611d9f565b92936001600160401b0360c0956104f798979482948752611f0681611d79565b602087015216604085015216606083015260808201528160a08201520190611dc3565b3461029a57602036600319011261029a57600435611f45613b83565b505f52600260205260405f2060405190611f5e82612712565b80548252610566600182015491611fa9611f99611f7b8560ff1690565b94611f8a602088019687613bc7565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a08201908152916120216118a8611fff600560048501549460c0870195865201612903565b9360e081019485525196519761201489611d79565b516001600160401b031690565b905191519260405196879687611ee6565b3461029a57606036600319011261029a5760043561204f81610289565b5f516020615d875f395f51905f526104766024359261206d84610289565b604435936120856001600160a01b0383161515613a0b565b612090851515613b37565b6120c46001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314613202565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611be58661213e6001600160a01b038516988995865f52600660205261211b8260405f205461211682821015613bd3565b61334c565b9788612138836001600160a01b03165f52600660205260405f2090565b556155d6565b6040519081529081906020820190565b3461029a5761215c3661083f565b61216d6008610bb660208401612bdf565b61217a6108d83684612c2e565b916121db61218a60208301612cb8565b9161219b608082013584868861472d565b6121a53685612fe4565b6121ae866149f3565b938685156122a4575b505060a081610e8c610e8561096f60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104ae57612218935f9361227f575b50612212903690612c2e565b866148e0565b1561224e576104767f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613090565b6104767f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613090565b61221291935061229d9060a03d60a011610b4c57610b3e818361277f565b9290612206565b610a3c6122c2926122b48661462c565b610f73366101408b01612f05565b505f866121b7565b9160a0936001600160401b03916104f797969385526122e881611d79565b602085015216604083015260608201528160808201520190611dc3565b3461029a57602036600319011261029a57600435612321613b83565b505f52600560205260405f206040519061233a8261272e565b80548252610566600182015491612371611f9960ff851694602087019561236081611d79565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123ca6123b5600560048501549460a0850195865201612903565b9160c081019283525194519561201487611d79565b9151905191604051958695866122ca565b60061115611d8357565b906006821015611d835752565b919260a06101209461240b85612474959a99989a6123e5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611dc3565b946101008201520152565b3461029a57602036600319011261029a576004355f60a06040516124a281612749565b82815282602082015282604082015282606082015282608082015201526124c7613b83565b505f525f6020526124da60405f20613bf5565b80516124e5816123db565b610566602083015192604081015190606061250d61186660808401516001600160401b031690565b91015191604051958695866123f2565b61253d61252936610da2565b61153b6002610bb660208496959601612bdf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047693610c9c925f926115f557506115ee3685612fe4565b3461029a576125a6366111ba565b6125ae615622565b6001600160a01b038116916125c4831515613a0b565b6001600160a01b03612601826125eb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b549161260e831515613b37565b5f61262e826125eb336001600160a01b03165f52600860205260405f2090565b55169181836126a857612651915f808080858a5af161264b613c52565b50613c81565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104d60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126b29184615680565b612651565b3461029a5761104d6126c83661104f565b91613ca9565b3461029a575f36600319011261029a57602060405160018152f35b156126f057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082b57604052565b60e081019081106001600160401b0382111761082b57604052565b60c081019081106001600160401b0382111761082b57604052565b60a081019081106001600160401b0382111761082b57604052565b90601f801991011681019081106001600160401b0382111761082b57604052565b604051906127af60408361277f565b565b604051906127af60e08361277f565b906040516127cd8161272e565b60c06004829461280a60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561285d575b602083101461284957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161283e565b5f92918154916128768361282f565b80835292600181169081156128cb575060011461289257505050565b5f9081526020812093945091925b8383106128b1575060209250010190565b6001816020929493945483858701015201910191906128a0565b915050602093945060ff929192191683830152151560051b010190565b906127af6128fc9260405193848092612867565b038361277f565b906040516129108161272e565b809260ff81546001600160401b038116845260401c1690600a821015611d8357600d6129819160c093602086015260018101546040860152612954600282016127c0565b6060860152612965600782016127c0565b6080860152612976600c82016128e8565b60a0860152016128e8565b910152565b5190600482101561029a57565b6001600160401b0381160361029a57565b5190811515820361029a57565b908160c091031261029a57612a1960a0604051926129ce84612749565b80518452602081015160208501526129e860408201612986565b604085015260608101516129fb81612993565b60608501526080810151612a0e81612993565b6080850152016129a4565b60a082015290565b908151612a2d81611d79565b815260806001600160401b0381612a53602086015160a0602087015260a0860190611dc3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f7928181520190612a21565b6040513d5f823e3d90fd5b90600d6104f792612ab781546001600160401b038116855260ff602086019160401c16611d92565b60018101546040840152612b236060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b9e6102608401600c8301612867565b9261024081850391015201612867565b906001600160401b03612bce602092959495604085526040850190612a8f565b9416910152565b600a111561029a57565b356104f781612bd5565b15612bf057565b633226144f60e21b5f5260045ffd5b15612c0657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029a57565b35906127af82612993565b91908260c091031261029a57604051612c4681612749565b60a08082948035612c5681612c15565b84526020810135612c6681610289565b60208501526040810135612c7981610289565b60408501526060810135612c8c81612993565b6060850152608081013560808501520135910152565b15612ca957565b631e40ad6360e31b5f5260045ffd5b356104f781610289565b908160a091031261029a5760405190612cda82612764565b80518252602081015160208301526040810151600681101561029a57612d1b9160809160408501526060810151612d1081612993565b6060850152016129a4565b608082015290565b90612d2f8183516123e5565b60806001600160401b0381612d53602086015160a0602087015260a0860190611dc3565b94604081015160408601526060810151606086015201511691015290565b35906127af82612bd5565b60c080916001600160401b038135612d9381612993565b1684526001600160a01b036020820135612dac81610289565b16602085015260ff612dc0604083016107a4565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561029a5701602081359101916001600160401b03821161029a57813603831361029a57565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f7916001600160401b038235612e5381612993565b168152612e716020830135612e6781612bd5565b6020830190611d92565b60408201356040820152612e8b6060820160608401612d7c565b612e9d61014082016101408401612d7c565b612ed1612ec5612eb1610220850185612deb565b610260610220860152610260850191612e1c565b92610240810190612deb565b91610240818503910152612e1c565b9091612ef76104f793604084526040840190612d23565b916020818403910152612e3c565b91908260e091031261029a57604051612f1d8161272e565b60c08082948035612f2d81612993565b84526020810135612f3d81610289565b6020850152612f4e604082016107a4565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082b57601f01601f191660200190565b929192612f9f82612f78565b91612fad604051938461277f565b82948184528183011161029a578281602093845f960137010152565b9080601f8301121561029a578160206104f793359101612f93565b9190916102608184031261029a57612ffa6127b1565b9261300482612c23565b845261301260208301612d71565b60208501526040820135604085015261302e8160608401612f05565b6060850152613041816101408401612f05565b60808501526102208201356001600160401b03811161029a5781613066918401612fc9565b60a08501526102408201356001600160401b03811161029a576130899201612fc9565b60c0830152565b9060206104f7928181520190612e3c565b60e09060a06104f7949363ffffffff81356130bb81612c15565b1683526001600160a01b0360208201356130d481610289565b1660208401526001600160a01b0360408201356130f081610289565b1660408401526001600160401b03606082013561310c81612993565b16606084015260808101356080840152013560a08201528160c08201520190612e3c565b356104f781612993565b9091612ef76104f793604084526040840190612a21565b634e487b7160e01b5f52603260045260245ffd5b60035481101561317d5760035f5260205f2001905f90565b613151565b805482101561317d575f5260205f2001905f90565b916131b09183549060031b91821b915f19901b19161790565b9055565b6003546801000000000000000081101561082b576001810160035560035481101561317d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b1561320957565b6370a8bfcd60e11b5f5260045ffd5b9060405161322581612749565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261327d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061329d60208361277f565b5f8252565b90916132b96104f793604084526040840190612d23565b916020818403910152611dc3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b03811161082b5760051b60200190565b6040519061330160208361277f565b5f808352366020840137565b90613317826132db565b613324604051918261277f565b8281528092613335601f19916132db565b0190602036910137565b91908201809211611bf857565b91908203918211611bf857565b805182101561317d5760209160051b010190565b91906003549080840293808504821490151715611bf857818410156133f157830190818411611bf8578082116133e9575b506133b16133ac848361334c565b61330d565b92805b8281106133c057505050565b806133cf6105e3600193613165565b6133e26133dc858461334c565b88613359565b52016133b4565b90505f61339e565b505090506104f76132f2565b906006811015611d835760ff80198354169116179055565b9060206104f7928181520190611dc3565b90613438825f525f60205260405f2090565b61344460018201613218565b91613450825460ff1690565b918461345e60058301612903565b91600261347560208801516001600160a01b031690565b9561347f816123db565b148061366e575b6135955750505061349e6001610bb660208401612bdf565b6134ae608084015183838761472d565b6134e160a0826134c661098661096f60808401612cb8565b604051632a2d120f60e21b8152938492839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104ae57610fbb61356f9461354b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613562965f92613574575b506135443689612fe4565b90866148e0565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613090565b0390a2565b61358e91925060a03d60a011610b4c57610b3e818361277f565b905f613539565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061366192935061356f946135f46014836135dc610fbb95600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61354b606086016136208151606061361660208301516001600160a01b031690565b9101519085614b03565b5160a061363760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614b03565b5060405191829182613415565b506014810154426001600160401b0390911610613486565b1561368d57565b6336c7a86b60e21b5f5260045ffd5b906136a681611d79565b60ff80198354169116179055565b9060206104f7928181520190612a8f565b908160a091031261029a57612d1b6080604051926136e284612764565b80518452602081015160208501526136fc60408201612986565b60408501526060810151612d1081612993565b90815161371b81611d79565b8152608080613739602085015160a0602086015260a0850190611dc3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ef76104f79360408452604084019061370f565b916137818284614c6d565b61396d57613797825f52600560205260405f2090565b906137a484835414613686565b600182018054929060026137c7600886901c6001600160a01b03165b9560ff1690565b6137d081611d79565b1480613955575b61386e57506002906137f06007610bb660208601612bdf565b0154906137ff8284838861472d565b61380e60a0826116f487614c8f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104ae577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461386994610c9c935f91611771575061176a3686612fe4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138c0600383016001600160401b03198154169055565b5f516020615d875f395f51905f526001600160a01b036139136138f1600c8601546001600160a01b039060401c1690565b9361390d856001600160a01b03165f52600660205260405f2090565b5461333f565b9283613930826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261394561449c565b61386960405192839201826136b4565b506003820154426001600160401b03909116106137d7565b613869816139a36007610bb660207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bdf565b610c926139b7865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861472d565b5f198114611bf85760010190565b9060206104f792818152019061370f565b156139fc57565b6306ee4dcd60e01b5f5260045ffd5b15613a1257565b63e6c4247b60e01b5f5260045ffd5b15613a295750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a4657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b0360206127af93613a9a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aca57565b637d95736160e01b5f5260045ffd5b6001600160401b03603c911601906001600160401b038211611bf857565b906001600160401b03809116911601906001600160401b038211611bf857565b906001600160401b03612bce602092959495604085526040850190612e3c565b15613b3e57565b6334b2073960e11b5f5260045ffd5b60405190613b5a8261272e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b908261272e565b606060c0835f81525f60208201525f6040820152613bac613b4d565b83820152613bb8613b4d565b60808201528260a08201520152565b613bd082611d79565b52565b15613bda57565b631e9acf1760e31b5f5260045ffd5b6006821015611d835752565b90604051613c0281612764565b60806001600160401b0360148395613c1e60ff82541686613be9565b613c2a60018201613218565b6020860152613c3b60058201612903565b604086015260138101546060860152015416910152565b3d15613c7c573d90613c6382612f78565b91613c71604051938461277f565b82523d5f602084013e565b606090565b15613c8a575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613cb482846156d9565b613e3a57613cca825f52600260205260405f2090565b90613cd784835414613686565b60018201805492906002613cf7600886901c6001600160a01b03166137c0565b613d0081611d79565b1480613e17575b613d995750600290613d206005610bb660208601612bdf565b015490613d2f8284838861472d565b613d3e60c082610cb9876140f3565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104ae577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461386994610c9c935f91610d405750610d303686612fe4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613e0f9060048301905f82549255613df8600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614b03565b61394561449c565b50600382015460401c6001600160401b03166001600160401b0342911610613d07565b613869816139a36005610bb660207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bdf565b15613e7757565b6306a41ced60e21b5f5260045ffd5b15613e8e5750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613ead575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613faa57801561317d57613f1f91843560f81c9081613f2357507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f3684613f3d949060ff161c90565b1614613e70565b613f9d613f558260ff165f52600760205260405f2090565b546001600160a01b0381169290613f8a90613f8590613f7684871515613e86565b60a01c6001600160401b031690565b613ad9565b906001600160401b038216421015613ea4565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b9081602091031261029a575190565b9392606093613ff36001600160a01b0394612bce949998998852608060208901526080880190611d9f565b918683036040880152612e1c565b919392959061400f906156f1565b916002821015611d83576020956001600160a01b03926140985761404b905b604051635850a09b60e11b81529889978896879560048701613fc8565b0392165afa80156104ae576127af915f91614069575b501515613a3f565b61408b915060203d602011614091575b614083818361277f565b810190613fb9565b5f614061565b503d614079565b5061404b7f000000000000000000000000000000000000000000000000000000000000000061402e565b604051906140cf82612764565b5f6080838281526140de613b83565b60208201528260408201528260608201520152565b6140fb6140c2565b905f5260026020526001600160401b0380600360405f2060ff60018201541661412381611d79565b855261413160058201612903565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bf8575f0390565b936141d494602094939682614184835f52600260205260405f2090565b9860a08701956141948751151590565b156144675760808201518901516001600160a01b0316998a975b60408a018d81516141be81611d79565b6141c781611d79565b614449575b505051151590565b614436575b50505050506141f260608401516001600160401b031690565b6001600160401b03811661440d575b5060038601805460808501516001600160401b039081169160401c168190036143d6575b50505f8351135f14614389576142479061423f8451615903565b9283916154a8565b6142566004860191825461333f565b90555b0180515f8113156142ee57505f516020615d875f395f51905f52916142866001600160a01b039251615903565b6142d760046142b0836142aa866001600160a01b03165f52600660205260405f2090565b5461334c565b96876142cd866001600160a01b03165f52600660205260405f2090565b550191825461333f565b90556040519384521691602090a25b6127af61449c565b90505f8112614300575b5050506142e6565b5f516020615d875f395f51905f52916143286143236001600160a01b0393614157565b615903565b614373600461434c8361390d866001600160a01b03165f52600660205260405f2090565b9687614369866001600160a01b03165f52600660205260405f2090565b550191825461334c565b90556040519384521691602090a25f80806142f8565b6143933415612bff565b8251905f82126143a6575b505050614259565b6143b56143236143bd93614157565b928391614b03565b6143cc6004860191825461334c565b9055825f8061439e565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614225565b6144309060038801906001600160401b03166001600160401b0319825416179055565b5f614201565b61443f94615809565b5f808281806141d9565b600161446092519161445a83611d79565b0161369c565b5f8d6141cc565b600c8b015460401c6001600160a01b0316998a976141ae565b9291906144976020916040865260408601906104b3565b930152565b6003546004545f92839082841115614606576144b8838561334c565b806040105f146145f857506144d26040959493929561330d565b925b808310806145ee575b156145e0576144ee6105e384613165565b614503610602825f52600260205260405f2090565b9561450d81615577565b6145cb5761451a816155a7565b15614579576001600160a01b03614561610602600198999a6106b0955f866106bf610666600c5f516020615d875f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144d4565b5050509391925061458990600455565b80614592575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145c660405192839283614480565b0390a1565b5050929394916145da906139d6565b9261456f565b509391925061458990600455565b50604085106144dd565b6144d290959493929561330d565b5f6144b8565b356104f781612c15565b1561461d57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561464281610289565b1661464e811515613a0b565b6001600160a01b03604083013561466481610289565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ec5781146146da5750806201518063ffffffff6146ad6127af9461460c565b16101590816146bd575b50614616565b62093a8091506146d163ffffffff9161460c565b1611155f6146b7565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029a57018035906001600160401b03821161029a5760200191813603831361029a57565b90916127af9361475d61476b926147528361474c6102208901896146fb565b90613ed0565b908888949394615967565b61474c6102408501856146fb565b91937f000000000000000000000000000000000000000000000000000000000000000093615967565b9060146001600160401b03916147a86140c2565b935f525f60205260405f20906147c260ff83541686613be9565b6147ce60058301612903565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148cf600185016148a261487960408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b9261491c8161496b946080946148fd885f525f60205260405f2090565b97614909895460ff1690565b614912816123db565b156149e1576152e2565b60408101805161492b816123db565b614934816123db565b1515806149b6575b61499c575b5060148401805460608301516001600160401b03908116911681900361497a575b50500151151590565b6149725750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614962565b6149b090516149aa816123db565b856133fd565b5f614941565b50845460ff168151906149c8826123db565b6149d1826123db565b6149da816123db565b141561493c565b6149ee8260018b016147ed565b6152e2565b805f525f60205260ff60405f2054166006811015611d83578015908115614a3b575b50614a36575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a48816123db565b145f614a15565b90614aa191805f525f602052614a6a600160405f2001613218565b60a083614a86614a7f61096f60808401612cb8565b5485614794565b604051632a2d120f60e21b8152968792839260048401612ee0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104ae576127af945f94614ade575b50614ad8903690612fe4565b916148e0565b614ad8919450614afc9060a03d60a011610b4c57610b3e818361277f565b9390614acc565b90614b169291614b11615622565b614b3c565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c68576001600160a01b0383169283614be0576001600160a01b038216925f8080808488620186a0f1614b73613c52565b5015614b80575050505050565b614bc3613869926125eb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bce82825461333f565b90556040519081529081906020820190565b614bf2614bee848484615afb565b1590565b614bfd575b50505050565b81614c466001600160a01b03926125eb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c5185825461333f565b90556040519384521691602090a35f808080614bf7565b505050565b905f52600560205260405f2054159081614c85575090565b6104f791506149f3565b614c976140c2565b905f5260056020526001600160401b03600360405f2060ff600182015416614cbe81611d79565b8452614ccc60058201612903565b60208501526004810154604085015201541660608201525f608082015290565b90614cf56140c2565b915f5260056020526001600160401b03600360405f2060ff600182015416614d1c81611d79565b8552614d2a60058201612903565b6020860152600481015460408601520154166060830152608082015290565b6020939291614dd491614d64815f52600560205260405f2090565b97604086018051614d7481611d79565b614d7d81611d79565b614e63575b5087856080880194614d948651151590565b614e50575b505050505060038701614db381546001600160401b031690565b60608601516001600160401b039081169116819003614e2e57505051151590565b15614e1557608001518201516001600160a01b031680935b8251905f821315614e0657614247915061423f8451615903565b5f82126143a657505050614259565b50600c84015460401c6001600160a01b03168093614dec565b815467ffffffffffffffff19166001600160401b039091161790555f806141cc565b614e5994615b68565b5f80878582614d99565b614e7a9051614e7181611d79565b60018b0161369c565b5f614d82565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f760a08261277f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615054575b806d04ee2d6d415b85acef8100000000600a921015615038575b662386f26fc10000811015615023575b6305f5e100811015615011575b612710811015615001575b6064811015614ff2575b1015614fe7575b614f7e6021614f4660018801615c26565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f8e57614f7e90614f4b565b50506001600160a01b03614fb384614fa7858498615bba565b60208151910120615c10565b911693168314614fdf57614fd1918160206114329351910120615c10565b14614fda575f90565b600190565b505050600190565b600190940193614f35565b60029060649004960195614f2e565b6004906127109004960195614f24565b6008906305f5e1009004960195614f19565b601090662386f26fc100009004960195614f0c565b6020906d04ee2d6d415b85acef81000000009004960195614efc565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ee2565b90600a811015611d835768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161515457505050565b5f5260205f20906020601f840160051c8301931061518c575b601f0160051c01905b818110615181575050565b5f8155600101615176565b909150819061516d565b91909182516001600160401b03811161082b576151bd816151b7845461282f565b84615147565b6020601f82116001146151f85781906131b09394955f926151ed575b50508160011b915f199060031b1c19161790565b015190505f806151d9565b601f1982169061520b845f5260205f2090565b915f5b8181106152455750958360019596971061522d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615223565b9192602060018192868b01518155019401920161520e565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d835760c0600d9161529c6127af958561507c565b604081015160018501556152b76060820151600286016150a9565b6152c86080820151600786016150a9565b6152d960a0820151600c8601615196565b01519101615196565b9161533160206152ff615323959694965f525f60205260405f2090565b9561531782606086015101516001600160a01b031690565b9586946005890161525d565b01516001600160a01b031690565b5f8351135f14615499576153458351615903565b6153508184846154a8565b61535f6013870191825461333f565b90555b602083019283515f8113615418575b5051905f82126153f0575b505050515f8112615393575b5050506127af61449c565b5f516020615d875f395f51905f52916153b66143236001600160a01b0393614157565b6153da601361434c8361390d866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f8080615388565b6143b56143236153ff93614157565b61540e6013850191825461334c565b9055815f8061537c565b61542190615903565b615440816142aa866001600160a01b03165f52600660205260405f2090565b908161545d866001600160a01b03165f52600660205260405f2090565b5561546d6013890191825461333f565b90556040519081526001600160a01b038416905f516020615d875f395f51905f5290602090a25f615371565b6154a33415612bff565b615362565b90614b1692916154b6615622565b908215614c68576001600160a01b03169182158015615568576154da823414612bff565b156154e457505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f5114811615615549575b6040919091525f6060521561552e5750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b600181151661555f573d15833b1515161661551c565b503d5f823e3d90fd5b6155723415612bff565b6154da565b6001015460ff1661558781611d79565b60038114908115615596575090565b600291506155a381611d79565b1490565b6001600160401b0360038201541642101590816155c2575090565b600180925060ff910154166155a381611d79565b90614b1692916155e4615622565b91908115614c68576001600160a01b03169182615619576127af92505f808080856001600160a01b0386165af161264b613c52565b6127af92615680565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156715760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156c3575b6040919091521561552e5750565b600181151661555f573d15833b151516166156b5565b905f52600260205260405f2054159081614c85575090565b6001600160401b03815116906020810151600a811015611d83576157988260406157f894015161573860806060840151930151946040519760208901526040880190611d92565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f76102408261277f565b9190915f52600260205260405f2091825560058201926158496001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d835760c06158ff9361586d6002976158b59461507c565b604081015160068701556158886060820151600788016150a9565b6158996080820151600c88016150a9565b6158aa60a082015160118801615196565b015160128501615196565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f811261590d5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061594e5750506127af9250038361277f565b8454835260019485019487945060209093019201615939565b6001600160a01b039061404b61598d61598860209895999697993690612fe4565b6156f1565b936040519889978896879563600109bb60e01b875260048701613fc8565b6001810190825f528160205260405f2054155f14615a135780546801000000000000000081101561082b57615a006159ea826001879401855584613182565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a41575f190190615a308282613182565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615af3575f198401848111611bf85783545f19810194908511611bf8575f958583615ab097615aa39503615ab6575b505050615a1a565b905f5260205260405f2090565b55600190565b615adc615ad691615acd6105e3615aea9588613182565b92839187613182565b90613197565b85905f5260205260405f2090565b555f8080615a9b565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b3960648261277f565b51908285620186a0f15f51913d9115615a13578115615b5f5750602011614a3657151590565b9150503b151590565b9190915f52600560205260405f2091825560058201926158496001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b6127af90615c02615bfc94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615ba8565b90615ba8565b03601f19810184528361277f565b6104f791615c1d91615c4e565b90929192615c88565b90615c3082612f78565b615c3d604051918261277f565b8281528092613335601f1991612f78565b8151919060418303615c7e57615c779250602082015190606060408401519301515f1a90615d04565b9192909190565b50505f9160029190565b615c9181611d79565b80615c9a575050565b615ca381611d79565b60018103615cba5763f645eedf60e01b5f5260045ffd5b615cc381611d79565b60028103615cde575063fce698f760e01b5f5260045260245ffd5b80615cea600392611d79565b14615cf25750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d7b579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104ae575f516001600160a01b03811615615d7157905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212205e68caa4b2d471ba6fc8e7a2cb4b8350ab1068b01b622da34e2d2daf0aa4dd6064736f6c634300081e0033000000000000000000000000b5e7d2b8db56a173ca8c05cddcc1379852cdc0950000000000000000000000002b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9", + "nonce": "0x13", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x32aae9", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xd382762780c6e0661cc1f7bb228f30f6e46461be9efacc3b54750dbe8d27dfa0", + "transactionIndex": "0xb", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27d2254", + "gasUsed": "0x1256cb", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x12f240", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x3e577828", + "l1GasUsed": "0xae0c", + "l1Fee": "0x25272cb0ee", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x382fe96", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x528301", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x3c6043743ee8f39a6fe2b4b40e236755c2dd7da980d4fc5340e3e44fe4f949e4", + "transactionIndex": "0x10", + "blockHash": "0xf4dd7882c48f7dd8930290c403c18113d391b9c1ceffd470061262875a859029", + "blockNumber": "0x27d2254", + "gasUsed": "0xb2090", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xd147c", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x3e577828", + "l1GasUsed": "0x7825", + "l1Fee": "0x19a59e8649", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x382fe96", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5d4cfb", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x3a642c4c020499885b2b289bfacabbf19b80a4165e7968109c0cc0536753ae7f", + "transactionIndex": "0x11", + "blockHash": "0xf4dd7882c48f7dd8930290c403c18113d391b9c1ceffd470061262875a859029", + "blockNumber": "0x27d2254", + "gasUsed": "0xac9fa", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xc91d2", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "l1GasPrice": "0x3e577828", + "l1GasUsed": "0x7371", + "l1Fee": "0x18a47d8152", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x382fe96", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xadbc13", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xb63e91129e4742f33a2e81449b74ddbe783a3c87188f4b2a73062089cae9a37d", + "transactionIndex": "0x12", + "blockHash": "0xf4dd7882c48f7dd8930290c403c18113d391b9c1ceffd470061262875a859029", + "blockNumber": "0x27d2254", + "gasUsed": "0x506f18", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x5e9a26", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x61b9e0767f2eca7e33802e82f9c64b1ebe72ba31", + "l1GasPrice": "0x3e577828", + "l1GasUsed": "0x364dd", + "l1Fee": "0xb9788de356", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x382fe96", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779278728832, + "chain": 84532, + "commit": "9110ba06" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779355068769.json b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779355068769.json new file mode 100644 index 000000000..2011ac31d --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779355068769.json @@ -0,0 +1,105 @@ +{ + "transactions": [ + { + "hash": "0xcbdad5d08d0ba793024eda3c0e46686c153117d68d62f59ba19a7624c41c9338", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xac00326e2d0c33253d77e22034291f4c2b6f3681", + "function": null, + "arguments": null, + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xa02b7de353bdd1a1d8443d8a20f81bcd88f1f2771d25c6b8768e1ea9d83038ee", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x6c333ac983749b9404704915ee6859bf1942a091", + "function": null, + "arguments": [ + "0xAc00326E2D0C33253d77E22034291F4c2b6F3681", + "0xc76632D91D45Ec88304ab2a983451d9EDf908C0d" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "gas": "0x686f87", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000ac00326e2d0c33253d77e22034291f4c2b6f3681000000000000000000000000c76632d91d45ec88304ab2a983451d9edf908c0d", + "nonce": "0x1", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x22693b", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xcbdad5d08d0ba793024eda3c0e46686c153117d68d62f59ba19a7624c41c9338", + "transactionIndex": "0x9", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27db76e", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x821ea", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "contractAddress": "0xac00326e2d0c33253d77e22034291f4c2b6f3681", + "l1GasPrice": "0x423a595b", + "l1GasUsed": "0x4ab6", + "l1Fee": "0x182bf4987", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x417d1ea", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x7475f6", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xa02b7de353bdd1a1d8443d8a20f81bcd88f1f2771d25c6b8768e1ea9d83038ee", + "transactionIndex": "0xc", + "blockHash": "0xaa9db51907099ffb747601afa87e8c0259eb351a0f80aadf5317e95f132bd1f5", + "blockNumber": "0x27db76e", + "gasUsed": "0x5055cb", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x5e57f2", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": null, + "contractAddress": "0x6c333ac983749b9404704915ee6859bf1942a091", + "l1GasPrice": "0x423a595b", + "l1GasUsed": "0x36276", + "l1Fee": "0xd17046c0c4", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x417d1ea", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779355068769, + "chain": 84532, + "commit": "5922d170" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779647695857.json b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779647695857.json new file mode 100644 index 000000000..bf9489539 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-1779647695857.json @@ -0,0 +1,105 @@ +{ + "transactions": [ + { + "hash": "0xdc8081368cdddc4941975b8cffafe0e9c8bfb4ef350a597187d5ef7fd3297942", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2446552603e20de6e253d0bb3919b0029013e28d8096341cc0a9639381848ab4", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xcd732", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xdc8081368cdddc4941975b8cffafe0e9c8bfb4ef350a597187d5ef7fd3297942", + "transactionIndex": "0x4", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff2f8", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x821ea", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "l1GasPrice": "0x424e7a30", + "l1GasUsed": "0x4ab6", + "l1Fee": "0x4f997f844", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3a864ca", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5d2d09", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x2446552603e20de6e253d0bb3919b0029013e28d8096341cc0a9639381848ab4", + "transactionIndex": "0x5", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff2f8", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x5e57f2", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "l1GasPrice": "0x424e7a30", + "l1GasUsed": "0x36276", + "l1Fee": "0x4f997f844", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3a864ca", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779647695857, + "chain": 84532, + "commit": "b88d511c" +} diff --git a/contracts/broadcast/DeployChannelHub.s.sol/84532/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-latest.json new file mode 100644 index 000000000..0c1d5ee4c --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/84532/run-latest.json @@ -0,0 +1,105 @@ +{ + "transactions": [ + { + "hash": "0xdc8081368cdddc4941975b8cffafe0e9c8bfb4ef350a597187d5ef7fd3297942", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "function": null, + "arguments": null, + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x84c87", + "value": "0x0", + "input": "0x6080806040523460155761069d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063600109bb146100f85763b0a141361461002f575f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f45761006090369060040161021c565b906044359067ffffffffffffffff82116100f4576020926100e361008b6100ec94369060040161021c565b6100dc600988610099610206565b97876040519889928484013781017f6368616c6c656e67650000000000000000000000000000000000000000000000838201520301601619810187520185610160565b36916101b2565b9060043561024a565b604051908152f35b5f80fd5b346100f45760803660031901126100f45760243567ffffffffffffffff81116100f4576101299036906004016101e8565b60443567ffffffffffffffff81116100f45760209161014f6100ec9236906004016101e8565b610157610206565b9160043561024a565b90601f8019910116810190811067ffffffffffffffff82111761018257604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161018257601f01601f191660200190565b9291926101be82610196565b916101cc6040519384610160565b8294818452818301116100f4578281602093845f960137010152565b9080601f830112156100f457816020610203933591016101b2565b90565b606435906001600160a01b03821682036100f457565b9181601f840112156100f45782359167ffffffffffffffff83116100f457602083818601950101116100f457565b92919083156102ce576001600160a01b038316156102bf576102ac60806102b1956020604051948592828401526040808401528051918291826060860152018484015e5f838284010152601f801991011681010301601f198101835282610160565b6102dd565b156102bb57600190565b5f90565b634501a91960e01b5f5260045ffd5b631c372f9f60e31b5f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015610504575b806d04ee2d6d415b85acef8100000000600a9210156104e9575b662386f26fc100008110156104d5575b6305f5e1008110156104c4575b6127108110156104b5575b60648110156104a7575b101561049d575b6001850190600a602161038661037085610196565b9461037e6040519687610160565b808652610196565b602085019890601f1901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a8353049081156103d257600a90610397565b50506001600160a01b035f936104518661045a946020610449869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f198101835282610160565b51902061052c565b90949194610566565b1694168414610494576001600160a01b03926104859261047c9251902061052c565b90929192610566565b161461048f575f90565b600190565b50505050600190565b936001019361035b565b606460029104960195610354565b6127106004910496019561034a565b6305f5e1006008910496019561033f565b662386f26fc1000060109104960195610332565b6d04ee2d6d415b85acef810000000060209104960195610322565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104610308565b815191906041830361055c576105559250602082015190606060408401519301515f1a906105da565b9192909190565b50505f9160029190565b60048110156105c65780610578575050565b6001810361058f5763f645eedf60e01b5f5260045ffd5b600281036105aa575063fce698f760e01b5f5260045260245ffd5b6003146105b45750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161065c579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610651575f516001600160a01b0381161561064757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea26469706673582212205df0280911c90687f0934e7459b8e54205afaacb95978d05b5782d1aba100c3464736f6c634300081e0033", + "nonce": "0x0", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2446552603e20de6e253d0bb3919b0029013e28d8096341cc0a9639381848ab4", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": null, + "arguments": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "transaction": { + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "gas": "0x686f97", + "value": "0x0", + "input": "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173dca4ab495188b545cfa919c0cb0a7e2280f2f4075af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173dccc09e335b87fb506c40a972e76fc7a225e0bf95af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b03817389b81857a46cf290f23f6ff9b24e1031aad652045af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033000000000000000000000000b1cdea413a080b3c6eb3d37e1c24d8cd10ce58440000000000000000000000006f375dad1ff0ad968bdde939f76a2b3d6b9d3ec5", + "nonce": "0x1", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xcd732", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xdc8081368cdddc4941975b8cffafe0e9c8bfb4ef350a597187d5ef7fd3297942", + "transactionIndex": "0x4", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff2f8", + "gasUsed": "0x66241", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x821ea", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "l1GasPrice": "0x424e7a30", + "l1GasUsed": "0x4ab6", + "l1Fee": "0x4f997f844", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3a864ca", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x5d2d09", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x2446552603e20de6e253d0bb3919b0029013e28d8096341cc0a9639381848ab4", + "transactionIndex": "0x5", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff2f8", + "gasUsed": "0x5055d7", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0x5e57f2", + "from": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "to": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "l1GasPrice": "0x424e7a30", + "l1GasUsed": "0x36276", + "l1Fee": "0x4f997f844", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3a864ca", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x89B81857A46cf290F23f6ff9B24e1031aAd65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0xDccc09e335B87FB506C40A972e76fC7a225E0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0xDcA4Ab495188b545cFa919C0CB0A7e2280F2F407" + ], + "pending": [], + "returns": {}, + "timestamp": 1779647695857, + "chain": 84532, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649477059.json b/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649477059.json new file mode 100644 index 000000000..37bf18cb7 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649477059.json @@ -0,0 +1,81 @@ +{ + "transactions": [ + { + "hash": "0x7bca7db564e2c166e682270e69fb919024969913cca7603e887c68cbfecff359", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x0000000000000000000000000000000000000000", + "30000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x10d6e", + "value": "0x6a94d74f430000", + "input": "0xb65b78d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a94d74f430000", + "nonce": "0xd", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xaae7cb", + "logs": [ + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000000006a94d74f430000", + "blockHash": "0xb7421f3a24a04ea8874eb6eae30fdab26aca51c14f5798aa5ea0dbb036de7fed", + "blockNumber": "0xa68821", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x7bca7db564e2c166e682270e69fb919024969913cca7603e887c68cbfecff359", + "transactionIndex": "0x5c", + "logIndex": "0xff", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000000006a94d74f430000", + "blockHash": "0xb7421f3a24a04ea8874eb6eae30fdab26aca51c14f5798aa5ea0dbb036de7fed", + "blockNumber": "0xa68821", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x7bca7db564e2c166e682270e69fb919024969913cca7603e887c68cbfecff359", + "transactionIndex": "0x5c", + "logIndex": "0x100", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000000000800000000008000000000000000000200000000000000000000000000000080000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020002000000000000000020000040000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x7bca7db564e2c166e682270e69fb919024969913cca7603e887c68cbfecff359", + "transactionIndex": "0x5c", + "blockHash": "0xb7421f3a24a04ea8874eb6eae30fdab26aca51c14f5798aa5ea0dbb036de7fed", + "blockNumber": "0xa68821", + "gasUsed": "0xc310", + "effectiveGasPrice": "0x455abfad", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649477059, + "chain": 11155111, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649512526.json b/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649512526.json new file mode 100644 index 000000000..a63f13c55 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649512526.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0xc6b529fe601bb8e93f5e197ded058c134b7aea50477fd64490f73d76aea2d60c", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x236eb848c95b231299b4aa9f56c73d6893462720", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x236eb848c95b231299b4aa9f56c73d6893462720", + "gas": "0xf821", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c300000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xe", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x1f5941cef717e50212ffac06262205f4061449444523a0179332261d9c740e92", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x236eB848C95b231299B4AA9f56c73D6893462720", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1a69a", + "value": "0x0", + "input": "0xb65b78d1000000000000000000000000236eb848c95b231299b4aa9f56c73d689346272000000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xf", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb171f3", + "logs": [ + { + "address": "0x236eb848c95b231299b4aa9f56c73d6893462720", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x3b0c17320802b364ff8c9f8c30af1557e44e44c70f3c79d9cbea5be25cd45e87", + "blockNumber": "0xa68824", + "blockTimestamp": "0x6a134be8", + "transactionHash": "0xc6b529fe601bb8e93f5e197ded058c134b7aea50477fd64490f73d76aea2d60c", + "transactionIndex": "0x62", + "logIndex": "0xee", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000000000000000000000000000000000000000000400000000000000000800000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000010000000000000400000000000000000000000000000000000000000000000", + "transactionHash": "0xc6b529fe601bb8e93f5e197ded058c134b7aea50477fd64490f73d76aea2d60c", + "transactionIndex": "0x62", + "blockHash": "0x3b0c17320802b364ff8c9f8c30af1557e44e44c70f3c79d9cbea5be25cd45e87", + "blockNumber": "0xa68824", + "gasUsed": "0xb3a5", + "effectiveGasPrice": "0x40609c54", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x236eb848c95b231299b4aa9f56c73d6893462720", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb2a3e8", + "logs": [ + { + "address": "0x236eb848c95b231299b4aa9f56c73d6893462720", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x3b0c17320802b364ff8c9f8c30af1557e44e44c70f3c79d9cbea5be25cd45e87", + "blockNumber": "0xa68824", + "blockTimestamp": "0x6a134be8", + "transactionHash": "0x1f5941cef717e50212ffac06262205f4061449444523a0179332261d9c740e92", + "transactionIndex": "0x63", + "logIndex": "0xef", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x000000000000000000000000236eb848c95b231299b4aa9f56c73d6893462720" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x3b0c17320802b364ff8c9f8c30af1557e44e44c70f3c79d9cbea5be25cd45e87", + "blockNumber": "0xa68824", + "blockTimestamp": "0x6a134be8", + "transactionHash": "0x1f5941cef717e50212ffac06262205f4061449444523a0179332261d9c740e92", + "transactionIndex": "0x63", + "logIndex": "0xf0", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x000000000000000000000000236eb848c95b231299b4aa9f56c73d6893462720" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x3b0c17320802b364ff8c9f8c30af1557e44e44c70f3c79d9cbea5be25cd45e87", + "blockNumber": "0xa68824", + "blockTimestamp": "0x6a134be8", + "transactionHash": "0x1f5941cef717e50212ffac06262205f4061449444523a0179332261d9c740e92", + "transactionIndex": "0x63", + "logIndex": "0xf1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000008400000000000000000000000000000000000200000000000000400000000000000000800000000008000000000000010000200000000000000000000000000002088000008000000000000020000000000000000000000100080000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000008000000000000040000000000000400000000000000000000000000000000000000000000000", + "transactionHash": "0x1f5941cef717e50212ffac06262205f4061449444523a0179332261d9c740e92", + "transactionIndex": "0x63", + "blockHash": "0x3b0c17320802b364ff8c9f8c30af1557e44e44c70f3c79d9cbea5be25cd45e87", + "blockNumber": "0xa68824", + "gasUsed": "0x131f5", + "effectiveGasPrice": "0x40609c54", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649512526, + "chain": 11155111, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649524812.json b/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649524812.json new file mode 100644 index 000000000..7bd42fdd1 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/11155111/run-1779649524812.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0xbba5b61f5466c8b592c8384dc7769702e8d2ba13cceb6de2b1ce50c5c8a60d15", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10641", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0x10", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c01e", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0x11", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1064bc1", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0xbba5b61f5466c8b592c8384dc7769702e8d2ba13cceb6de2b1ce50c5c8a60d15", + "transactionIndex": "0x5e", + "logIndex": "0x106", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xbba5b61f5466c8b592c8384dc7769702e8d2ba13cceb6de2b1ce50c5c8a60d15", + "transactionIndex": "0x5e", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "gasUsed": "0xb352", + "effectiveGasPrice": "0x3f687d39", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x177b656", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "logIndex": "0x18d", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "logIndex": "0x18e", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "logIndex": "0x18f", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "gasUsed": "0x1446f", + "effectiveGasPrice": "0x3f687d39", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649524812, + "chain": 11155111, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/11155111/run-latest.json b/contracts/broadcast/DepositToNode.s.sol/11155111/run-latest.json new file mode 100644 index 000000000..7bd42fdd1 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/11155111/run-latest.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0xbba5b61f5466c8b592c8384dc7769702e8d2ba13cceb6de2b1ce50c5c8a60d15", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10641", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0x10", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c01e", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0x11", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1064bc1", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0xbba5b61f5466c8b592c8384dc7769702e8d2ba13cceb6de2b1ce50c5c8a60d15", + "transactionIndex": "0x5e", + "logIndex": "0x106", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xbba5b61f5466c8b592c8384dc7769702e8d2ba13cceb6de2b1ce50c5c8a60d15", + "transactionIndex": "0x5e", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "gasUsed": "0xb352", + "effectiveGasPrice": "0x3f687d39", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x177b656", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "logIndex": "0x18d", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "logIndex": "0x18e", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "blockTimestamp": "0x6a134bf4", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "logIndex": "0x18f", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6f2f513c17f62cccfe8d4fa1194be653c47445be264dbb40433d665d853ae8ed", + "transactionIndex": "0x8b", + "blockHash": "0x222567d0da9e3013a4de873e3e648d36b1be0ff5648670b2524361263152617a", + "blockNumber": "0xa68825", + "gasUsed": "0x1446f", + "effectiveGasPrice": "0x3f687d39", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649524812, + "chain": 11155111, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/1449000/run-1779649489951.json b/contracts/broadcast/DepositToNode.s.sol/1449000/run-1779649489951.json new file mode 100644 index 000000000..698e2b035 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/1449000/run-1779649489951.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0x615cf314bb9e3584b085a060dd5e1bac0821ffc15bd2596ab1f44353179eb757", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10687", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c300000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xc", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c060", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde00000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xd", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb382", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0x615cf314bb9e3584b085a060dd5e1bac0821ffc15bd2596ab1f44353179eb757", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x615cf314bb9e3584b085a060dd5e1bac0821ffc15bd2596ab1f44353179eb757", + "transactionIndex": "0x0", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "gasUsed": "0xb382", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1449f", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "gasUsed": "0x1449f", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649489951, + "chain": 1449000, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/1449000/run-latest.json b/contracts/broadcast/DepositToNode.s.sol/1449000/run-latest.json new file mode 100644 index 000000000..698e2b035 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/1449000/run-latest.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0x615cf314bb9e3584b085a060dd5e1bac0821ffc15bd2596ab1f44353179eb757", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10687", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c300000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xc", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c060", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde00000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xd", + "chainId": "0x161c28" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb382", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0x615cf314bb9e3584b085a060dd5e1bac0821ffc15bd2596ab1f44353179eb757", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x615cf314bb9e3584b085a060dd5e1bac0821ffc15bd2596ab1f44353179eb757", + "transactionIndex": "0x0", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "gasUsed": "0xb382", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1449f", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "blockTimestamp": "0x6a134bc9", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0xeaf11487a13b8ae81c802b68450eb40b9f8449787e776b2b25576c0b8eb03827", + "transactionIndex": "0x1", + "blockHash": "0x439c89a245e20325b695a663eef6052bf54cacb147818fad33516a800806f8e6", + "blockNumber": "0x6a2230", + "gasUsed": "0x1449f", + "effectiveGasPrice": "0x2ecc0e88", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649489951, + "chain": 1449000, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/59141/run-1779649471645.json b/contracts/broadcast/DepositToNode.s.sol/59141/run-1779649471645.json new file mode 100644 index 000000000..1e14632c7 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/59141/run-1779649471645.json @@ -0,0 +1,81 @@ +{ + "transactions": [ + { + "hash": "0x6410887b3ba0e1e99c3ebee574c33ce0dba7289a2fe07d70ceb44e0306649f1d", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x0000000000000000000000000000000000000000", + "30000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x10d6e", + "value": "0x6a94d74f430000", + "input": "0xb65b78d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a94d74f430000", + "nonce": "0xa", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xc310", + "logs": [ + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000000006a94d74f430000", + "blockHash": "0x6c4052e071d8963b483b77d41586aeecdfcb9ef224f248e99a208bbff1a5e8fc", + "blockNumber": "0x1c597b1", + "blockTimestamp": "0x6a134bbc", + "transactionHash": "0x6410887b3ba0e1e99c3ebee574c33ce0dba7289a2fe07d70ceb44e0306649f1d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000000006a94d74f430000", + "blockHash": "0x6c4052e071d8963b483b77d41586aeecdfcb9ef224f248e99a208bbff1a5e8fc", + "blockNumber": "0x1c597b1", + "blockTimestamp": "0x6a134bbc", + "transactionHash": "0x6410887b3ba0e1e99c3ebee574c33ce0dba7289a2fe07d70ceb44e0306649f1d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000000000800000000008000000000000000000200000000000000000000000000000080000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020002000000000000000020000040000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6410887b3ba0e1e99c3ebee574c33ce0dba7289a2fe07d70ceb44e0306649f1d", + "transactionIndex": "0x0", + "blockHash": "0x6c4052e071d8963b483b77d41586aeecdfcb9ef224f248e99a208bbff1a5e8fc", + "blockNumber": "0x1c597b1", + "gasUsed": "0xc310", + "effectiveGasPrice": "0x2331c40", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649471645, + "chain": 59141, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/59141/run-1779649478380.json b/contracts/broadcast/DepositToNode.s.sol/59141/run-1779649478380.json new file mode 100644 index 000000000..369cc9638 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/59141/run-1779649478380.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0xb5235f07e3fc3190b043aa5dafb96dcedf63c51f6b937a24934892544be82ae9", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10687", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c300000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xb", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c060", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde00000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xc", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb382", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0xb5235f07e3fc3190b043aa5dafb96dcedf63c51f6b937a24934892544be82ae9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xb5235f07e3fc3190b043aa5dafb96dcedf63c51f6b937a24934892544be82ae9", + "transactionIndex": "0x0", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "gasUsed": "0xb382", + "effectiveGasPrice": "0x2331c40", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1f821", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "gasUsed": "0x1449f", + "effectiveGasPrice": "0x2331c40", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649478380, + "chain": 59141, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/59141/run-latest.json b/contracts/broadcast/DepositToNode.s.sol/59141/run-latest.json new file mode 100644 index 000000000..369cc9638 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/59141/run-latest.json @@ -0,0 +1,152 @@ +{ + "transactions": [ + { + "hash": "0xb5235f07e3fc3190b043aa5dafb96dcedf63c51f6b937a24934892544be82ae9", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10687", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c300000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xb", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c060", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde00000000000000000000000000000000000000000000152d02c7e14af6800000", + "nonce": "0xc", + "chainId": "0xe705" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb382", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0xb5235f07e3fc3190b043aa5dafb96dcedf63c51f6b937a24934892544be82ae9", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0xb5235f07e3fc3190b043aa5dafb96dcedf63c51f6b937a24934892544be82ae9", + "transactionIndex": "0x0", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "gasUsed": "0xb382", + "effectiveGasPrice": "0x2331c40", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x1f821", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x00000000000000000000000000000000000000000000152d02c7e14af6800000", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "blockTimestamp": "0x6a134bc4", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0x3f37ed6e0531ae0c74e1c8d3e7702ca2d9edc48e626cc953bf2bd56ddae802bd", + "transactionIndex": "0x1", + "blockHash": "0x60aee114d1a9f3c6da525b47aa2a8200f825e9126ae368949547fd7b1783fc5c", + "blockNumber": "0x1c597b3", + "gasUsed": "0x1449f", + "effectiveGasPrice": "0x2331c40", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649478380, + "chain": 59141, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/80002/run-1779649469948.json b/contracts/broadcast/DepositToNode.s.sol/80002/run-1779649469948.json new file mode 100644 index 000000000..a9a827af8 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/80002/run-1779649469948.json @@ -0,0 +1,115 @@ +{ + "transactions": [ + { + "hash": "0xc40ed5f8e340b8a3ed95e692bc5208245951b5cd9d685677931f47d6f9d0b499", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x0000000000000000000000000000000000000000", + "30000000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x10d8e", + "value": "0x1a055690d9db80000", + "input": "0xb65b78d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a055690d9db80000", + "nonce": "0x9", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xdd68", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0xe6497e3ee548a3372136af2fcb0696db31fc6cf20260707645068bd3fe97f3c4", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000000000000000000000000001b38adb1c3438bb7900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001335720e9680bb79000000000000000000000000000000000000000000000001a055690d9db80000", + "blockHash": "0xfac3815600914f84af0be6b0e4dfcb389e878b0c4602ad265ecd52c3156e87c3", + "blockNumber": "0x252446c", + "blockTimestamp": "0x6a134bbf", + "transactionHash": "0xc40ed5f8e340b8a3ed95e692bc5208245951b5cd9d685677931f47d6f9d0b499", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000001a055690d9db80000", + "blockHash": "0xfac3815600914f84af0be6b0e4dfcb389e878b0c4602ad265ecd52c3156e87c3", + "blockNumber": "0x252446c", + "blockTimestamp": "0x6a134bbf", + "transactionHash": "0xc40ed5f8e340b8a3ed95e692bc5208245951b5cd9d685677931f47d6f9d0b499", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000001a055690d9db80000", + "blockHash": "0xfac3815600914f84af0be6b0e4dfcb389e878b0c4602ad265ecd52c3156e87c3", + "blockNumber": "0x252446c", + "blockTimestamp": "0x6a134bbf", + "transactionHash": "0xc40ed5f8e340b8a3ed95e692bc5208245951b5cd9d685677931f47d6f9d0b499", + "transactionIndex": "0x0", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000000104c2545b1d020000000000000000000000000000000000000000000000001b39eb28ebd9b4383000000000000000000000000000000000000000000004c90979af12c953817b0000000000000000000000000000000000000000000000001b38e666977e97363000000000000000000000000000000000000000000004c9097ab3d51dae9e7d0", + "blockHash": "0xfac3815600914f84af0be6b0e4dfcb389e878b0c4602ad265ecd52c3156e87c3", + "blockNumber": "0x252446c", + "blockTimestamp": "0x6a134bbf", + "transactionHash": "0xc40ed5f8e340b8a3ed95e692bc5208245951b5cd9d685677931f47d6f9d0b499", + "transactionIndex": "0x0", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000100000000000008000000000000000000020000000000000000000000000000000400000800000000000000000000100000000200000000000020000000000000000000800000000008000000080000000000200000000000000000000020000000488000008000000000000000000000000000000200000100000000000000000000000000000000000000000000000000000004000000000000000000001000000000000000000420002800000108000000020000040000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0xc40ed5f8e340b8a3ed95e692bc5208245951b5cd9d685677931f47d6f9d0b499", + "transactionIndex": "0x0", + "blockHash": "0xfac3815600914f84af0be6b0e4dfcb389e878b0c4602ad265ecd52c3156e87c3", + "blockNumber": "0x252446c", + "gasUsed": "0xdd68", + "effectiveGasPrice": "0x12d80662d3", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649469948, + "chain": 80002, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/80002/run-1779649475017.json b/contracts/broadcast/DepositToNode.s.sol/80002/run-1779649475017.json new file mode 100644 index 000000000..51e76d22c --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/80002/run-1779649475017.json @@ -0,0 +1,186 @@ +{ + "transactions": [ + { + "hash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x1b66e510b845a746334b866b62c64746bebff857", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "10000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x1b66e510b845a746334b866b62c64746bebff857", + "gas": "0xf7bf", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000009184e72a000", + "nonce": "0xa", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x1b66e510B845a746334B866b62C64746bEbfF857", + "10000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1da8d", + "value": "0x0", + "input": "0xb65b78d10000000000000000000000001b66e510b845a746334b866b62c64746bebff857000000000000000000000000000000000000000000000000000009184e72a000", + "nonce": "0xb", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb6a6", + "logs": [ + { + "address": "0x1b66e510b845a746334b866b62c64746bebff857", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000000d71cc9e3923f80000000000000000000000000000000000000000000000001338fd5bd9faf6cb000000000000000000000000000000000000000000004c9097d924d068cdb050000000000000000000000000000000000000000000000000132b8b8f3bc1d2d3000000000000000000000000000000000000000000004c9097e6969d0706d448", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000108000000000000000000020200000000000000000000000000004400000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000408000000000000000000000000000000000000220000000000000000000000000000000000002000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000010000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionIndex": "0x0", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "gasUsed": "0xb6a6", + "effectiveGasPrice": "0x12d80662d3", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x1b66e510b845a746334b866b62c64746bebff857", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x23979", + "logs": [ + { + "address": "0x1b66e510b845a746334b866b62c64746bebff857", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x0000000000000000000000001b66e510b845a746334b866b62c64746bebff857" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x0000000000000000000000001b66e510b845a746334b866b62c64746bebff857" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x4", + "removed": false + }, + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000001c7941ade667fc000000000000000000000000000000000000000000000000132b8b8f3b94dff9000000000000000000000000000000000000000000004c9097e6969d0706d448000000000000000000000000000000000000000000000000130f124d8dae77fd000000000000000000000000000000000000000000004c9098030fdeb4ed3c44", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x5", + "removed": false + } + ], + "logsBloom": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000008800000000000000010800000000000000000002000000000000000000000000000000c400000800000000000000000000100000000200000000000000000000000000000000800000000008000000080000010000200000000000000000000020000000488000008080000000000000000000000000000200000100000000000000000000000000000002000000000000000000000004000000002000004000001000000000000000000420002000000100000000000000040000000000002000000000000000000000000000000000000000000100000", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "gasUsed": "0x182d3", + "effectiveGasPrice": "0x12d80662d3", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649475017, + "chain": 80002, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/80002/run-latest.json b/contracts/broadcast/DepositToNode.s.sol/80002/run-latest.json new file mode 100644 index 000000000..51e76d22c --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/80002/run-latest.json @@ -0,0 +1,186 @@ +{ + "transactions": [ + { + "hash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x1b66e510b845a746334b866b62c64746bebff857", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "10000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x1b66e510b845a746334b866b62c64746bebff857", + "gas": "0xf7bf", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000009184e72a000", + "nonce": "0xa", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x1b66e510B845a746334B866b62C64746bEbfF857", + "10000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1da8d", + "value": "0x0", + "input": "0xb65b78d10000000000000000000000001b66e510b845a746334b866b62c64746bebff857000000000000000000000000000000000000000000000000000009184e72a000", + "nonce": "0xb", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0xb6a6", + "logs": [ + { + "address": "0x1b66e510b845a746334b866b62c64746bebff857", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000000d71cc9e3923f80000000000000000000000000000000000000000000000001338fd5bd9faf6cb000000000000000000000000000000000000000000004c9097d924d068cdb050000000000000000000000000000000000000000000000000132b8b8f3bc1d2d3000000000000000000000000000000000000000000004c9097e6969d0706d448", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000108000000000000000000020200000000000000000000000000004400000800000000000000000000100000000000000000000000000000000000000000800000000000000000080000000000000000000000000000000020000000408000000000000000000000000000000000000220000000000000000000000000000000000002000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000010000000000000000000000000000000000000000000000000000000100000", + "transactionHash": "0xb17911e0336157b88fb67f136aabb999654685a0525817374df5bcf0b42c2464", + "transactionIndex": "0x0", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "gasUsed": "0xb6a6", + "effectiveGasPrice": "0x12d80662d3", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x1b66e510b845a746334b866b62c64746bebff857", + "contractAddress": null + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x23979", + "logs": [ + { + "address": "0x1b66e510b845a746334b866b62c64746bebff857", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x0000000000000000000000001b66e510b845a746334b866b62c64746bebff857" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x0000000000000000000000001b66e510b845a746334b866b62c64746bebff857" + ], + "data": "0x000000000000000000000000000000000000000000000000000009184e72a000", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x4", + "removed": false + }, + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000001c7941ade667fc000000000000000000000000000000000000000000000000132b8b8f3b94dff9000000000000000000000000000000000000000000004c9097e6969d0706d448000000000000000000000000000000000000000000000000130f124d8dae77fd000000000000000000000000000000000000000000004c9098030fdeb4ed3c44", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "blockTimestamp": "0x6a134bc3", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "logIndex": "0x5", + "removed": false + } + ], + "logsBloom": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000008800000000000000010800000000000000000002000000000000000000000000000000c400000800000000000000000000100000000200000000000000000000000000000000800000000008000000080000010000200000000000000000000020000000488000008080000000000000000000000000000200000100000000000000000000000000000002000000000000000000000004000000002000004000001000000000000000000420002000000100000000000000040000000000002000000000000000000000000000000000000000000100000", + "transactionHash": "0x9358642a73b5552d4b4e2ebd14efd034b8cefe0163b0ce7af1eb3d8cb0a5ef45", + "transactionIndex": "0x1", + "blockHash": "0x4a169448096f3732ff3465eb86e03a240be254f8056db9d5be96922b2df56e0f", + "blockNumber": "0x252446f", + "gasUsed": "0x182d3", + "effectiveGasPrice": "0x12d80662d3", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649475017, + "chain": 80002, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649469022.json b/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649469022.json new file mode 100644 index 000000000..a92bc8065 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649469022.json @@ -0,0 +1,89 @@ +{ + "transactions": [ + { + "hash": "0x32afcec622bd09789a64c1a1140ee7b1ff3e3ede50327a9db77add0bcc0fb170", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x0000000000000000000000000000000000000000", + "30000000000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x10d6e", + "value": "0x6a94d74f430000", + "input": "0xb65b78d10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a94d74f430000", + "nonce": "0x9", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x3fe7b2", + "logs": [ + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000000006a94d74f430000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff66f", + "blockTimestamp": "0x6a134bbe", + "transactionHash": "0x32afcec622bd09789a64c1a1140ee7b1ff3e3ede50327a9db77add0bcc0fb170", + "transactionIndex": "0x11", + "logIndex": "0xdf", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x000000000000000000000000000000000000000000000000006a94d74f430000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff66f", + "blockTimestamp": "0x6a134bbe", + "transactionHash": "0x32afcec622bd09789a64c1a1140ee7b1ff3e3ede50327a9db77add0bcc0fb170", + "transactionIndex": "0x11", + "logIndex": "0xe0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000000000800000000008000000000000000000200000000000000000000000000000080000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020002000000000000000020000040000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x32afcec622bd09789a64c1a1140ee7b1ff3e3ede50327a9db77add0bcc0fb170", + "transactionIndex": "0x11", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff66f", + "gasUsed": "0xc310", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xae38", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null, + "l1GasPrice": "0x41d8c8b0", + "l1GasUsed": "0x640", + "l1Fee": "0x1664244b6", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3abe641", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649469022, + "chain": 84532, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649483062.json b/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649483062.json new file mode 100644 index 000000000..9faa6df27 --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649483062.json @@ -0,0 +1,57 @@ +{ + "transactions": [ + { + "hash": "0x42e66e394e62c4e0159063e9f3cd20ad164f0e6a7b4dc7b37b03a13f4231bb3e", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10641", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0x9", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": null, + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c01e", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0xa", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [], + "pending": [ + "0x42e66e394e62c4e0159063e9f3cd20ad164f0e6a7b4dc7b37b03a13f4231bb3e" + ], + "returns": {}, + "timestamp": 1779649483062, + "chain": 84532, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649780158.json b/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649780158.json new file mode 100644 index 000000000..b298b5adc --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/84532/run-1779649780158.json @@ -0,0 +1,168 @@ +{ + "transactions": [ + { + "hash": "0x6aa24ec06eca65b08571100428ca4611fc2c27a1682d50c36a17ca86860c7ddc", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10641", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0xb", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c01e", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0xc", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x78f46", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x6aa24ec06eca65b08571100428ca4611fc2c27a1682d50c36a17ca86860c7ddc", + "transactionIndex": "0x2", + "logIndex": "0xa", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6aa24ec06eca65b08571100428ca4611fc2c27a1682d50c36a17ca86860c7ddc", + "transactionIndex": "0x2", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff70a", + "gasUsed": "0xb352", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xae38", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null, + "l1GasPrice": "0x3c063a5b", + "l1GasUsed": "0x640", + "l1Fee": "0x1617f994a", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3c1394e", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x8d3b5", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "logIndex": "0xb", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "logIndex": "0xc", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "logIndex": "0xd", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "gasUsed": "0x1446f", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xae38", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null, + "l1GasPrice": "0x3c063a5b", + "l1GasUsed": "0x640", + "l1Fee": "0x1617f994a", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3c1394e", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649780158, + "chain": 84532, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/broadcast/DepositToNode.s.sol/84532/run-latest.json b/contracts/broadcast/DepositToNode.s.sol/84532/run-latest.json new file mode 100644 index 000000000..b298b5adc --- /dev/null +++ b/contracts/broadcast/DepositToNode.s.sol/84532/run-latest.json @@ -0,0 +1,168 @@ +{ + "transactions": [ + { + "hash": "0x6aa24ec06eca65b08571100428ca4611fc2c27a1682d50c36a17ca86860c7ddc", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "function": "approve(address,uint256)", + "arguments": [ + "0x5DBa8515af063Db0c243C15eCE7B99F91459C7C3", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "gas": "0x10641", + "value": "0x0", + "input": "0x095ea7b30000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0xb", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "function": "depositToNode(address,uint256)", + "arguments": [ + "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "100000000000" + ], + "transaction": { + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "gas": "0x1c01e", + "value": "0x0", + "input": "0xb65b78d100000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde000000000000000000000000000000000000000000000000000000174876e800", + "nonce": "0xc", + "chainId": "0x14a34" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x78f46", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x6aa24ec06eca65b08571100428ca4611fc2c27a1682d50c36a17ca86860c7ddc", + "transactionIndex": "0x2", + "logIndex": "0xa", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020200000000000000000000000000000400000000000008000000000000000000000000000000000000800000000000000000800000000000000000000000000000000000000000000002000000000000008000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", + "transactionHash": "0x6aa24ec06eca65b08571100428ca4611fc2c27a1682d50c36a17ca86860c7ddc", + "transactionIndex": "0x2", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x27ff70a", + "gasUsed": "0xb352", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xae38", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "contractAddress": null, + "l1GasPrice": "0x3c063a5b", + "l1GasUsed": "0x640", + "l1Fee": "0x1617f994a", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3c1394e", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + }, + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x8d3b5", + "logs": [ + { + "address": "0x67a5f0b0553e22689a539b80cbe4a1cd126e2bde", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "0x0000000000000000000000005dba8515af063db0c243c15ece7b99f91459c7c3" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "logIndex": "0xb", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "logIndex": "0xc", + "removed": false + }, + { + "address": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "topics": [ + "0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce", + "0x00000000000000000000000067a5f0b0553e22689a539b80cbe4a1cd126e2bde" + ], + "data": "0x000000000000000000000000000000000000000000000000000000174876e800", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "blockTimestamp": "0x6a134cf4", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "logIndex": "0xd", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000020000000000000000000000000000808400000000000008000000000000000000000200000000080000800000000000000000800000000008000000000000010000200000000000000002000000000000088000008000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020002000000000000000000000040000000200000000000000000000000000000000000000000000000000000", + "transactionHash": "0x31f569f97a93ad5c4260dbc62dc8737f09ee85b5d094df1c624d8314d7ecd704", + "transactionIndex": "0x3", + "blockHash": "0xe11de9940f19fecb57bde312dbd9ee4e85c916b8b27f3e082ddc910ebb3b6ece", + "blockNumber": "0x27ff70a", + "gasUsed": "0x1446f", + "effectiveGasPrice": "0x5b8d80", + "blobGasUsed": "0xae38", + "from": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "to": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "contractAddress": null, + "l1GasPrice": "0x3c063a5b", + "l1GasUsed": "0x640", + "l1Fee": "0x1617f994a", + "l1BaseFeeScalar": "0x44d", + "l1BlobBaseFee": "0x3c1394e", + "l1BlobBaseFeeScalar": "0xa118b", + "daFootprintGasScalar": "0x1be" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1779649780158, + "chain": 84532, + "commit": "b88d511c" +} \ No newline at end of file diff --git a/contracts/deployments/1/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T11-48-25.json b/contracts/deployments/1/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T11-48-25.json new file mode 100644 index 000000000..922dc72fd --- /dev/null +++ b/contracts/deployments/1/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T11-48-25.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x25567dbd47b3982a2dcf338637091193c8495b7ab1dd9d50a11f39340cb39ed4", + "commit": "e07ad9c2", + "timestamp": 1779450505, + "chainId": 1, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T11-48-25.json b/contracts/deployments/1/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T11-48-25.json new file mode 100644 index 000000000..548816014 --- /dev/null +++ b/contracts/deployments/1/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T11-48-25.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0x1637d08de07708ff81c29bc260edbcdfffae06b4c0dca1283c4ec1909e483574", + "commit": "e07ad9c2", + "timestamp": 1779450505, + "chainId": 1, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1/ECDSASignatureValidator/prod-v1_3_0-2026-05-22T11-48-25.json b/contracts/deployments/1/ECDSASignatureValidator/prod-v1_3_0-2026-05-22T11-48-25.json new file mode 100644 index 000000000..37092c6bf --- /dev/null +++ b/contracts/deployments/1/ECDSASignatureValidator/prod-v1_3_0-2026-05-22T11-48-25.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0xca8853654985715283a8faa4a6b71d1c8de4eb078b8f2b910cf29918c66da30c", + "commit": "e07ad9c2", + "timestamp": 1779450505, + "chainId": 1, + "contractPath": "ECDSASignatureValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T11-48-25.json b/contracts/deployments/1/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T11-48-25.json new file mode 100644 index 000000000..1a55e22e3 --- /dev/null +++ b/contracts/deployments/1/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T11-48-25.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x0b0f8b27f8ec602b78774f802237a332a2520c950fe5e530cccf385a36c5a47a", + "commit": "e07ad9c2", + "timestamp": 1779450505, + "chainId": 1, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T11-48-25.json b/contracts/deployments/1/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T11-48-25.json new file mode 100644 index 000000000..d34f2fb5f --- /dev/null +++ b/contracts/deployments/1/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T11-48-25.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0xeccf08e1435693e10907996a5271c68064579ef9e5ae183cf6392da3ab637bb2", + "commit": "e07ad9c2", + "timestamp": 1779450505, + "chainId": 1, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T11-55-37.json b/contracts/deployments/1/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T11-55-37.json new file mode 100644 index 000000000..468fb7494 --- /dev/null +++ b/contracts/deployments/1/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T11-55-37.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0xc189c67a0089bd264f8e195c1cd66490e606565907518640454de794e3880bb8", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779450937, + "chainId": 1, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/2026-04-20T09-12-12.json b/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/2026-04-20T09-12-12.json new file mode 100644 index 000000000..6caf1bf88 --- /dev/null +++ b/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/2026-04-20T09-12-12.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x353a207a7bc822d8d3e58bca2f3f9e2b90b26e78", + "transactionHash": "0xf45edda1c47a54a956746235cc1f700bf82baae2c675deb94289fee2ad31870e", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776676332, + "chainId": 11155111, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/dev-v1_3_0-2026-05-20T12-00-01.json b/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/dev-v1_3_0-2026-05-20T12-00-01.json new file mode 100644 index 000000000..5edc88850 --- /dev/null +++ b/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/dev-v1_3_0-2026-05-20T12-00-01.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0xce57f0cc9f8184819745729a47b8f4a399b1402557a979062a42fe4f7dab90d9", + "commit": "9110ba06", + "timestamp": 1779278401, + "chainId": 11155111, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "stress and sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/2026-04-20T09-13-24.json b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/2026-04-20T09-13-24.json new file mode 100644 index 000000000..499eb8fec --- /dev/null +++ b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/2026-04-20T09-13-24.json @@ -0,0 +1,16 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x3497229ed24ad7877160923A39B982FEd7a91e31", + "transactionHash": "0x54f8602c28e2937a0d52420a605ce41e551e2773a45e1ac141a25941592f9a23", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776676404, + "chainId": 11155111, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": ["0x165fe1a7d70DcF7594442CA9E2A572aA43b79E07", "0x2b6dc5bb33f3eaabfd3a8d17fdb7bdb8fef331f9"], + "libraries": { + "src/ChannelEngine.sol:ChannelEngine": "0x353A207A7bC822D8d3E58bcA2f3F9E2b90B26e78", + "src/EscrowDepositEngine.sol:EscrowDepositEngine": "0xb5c2e8BAD7417E654E9087753EC1D08762a06f91", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine": "0xaf9F833141094083B9de2cb6AA0f7E1f2d2DEee1" + }, + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-45-36.json b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-45-36.json new file mode 100644 index 000000000..87f5f33e4 --- /dev/null +++ b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-45-36.json @@ -0,0 +1,14 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "transactionHash": "0xa15ebf0cadb7273349070952636edfb166053f09f6e52fe57e73bfa02e1ce54a", + "commit": "b88d511c", + "timestamp": 1779648336, + "chainId": 11155111, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/stress-v1_3_0-2026-05-20T14-12-01.json b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/stress-v1_3_0-2026-05-20T14-12-01.json new file mode 100644 index 000000000..c28c40f32 --- /dev/null +++ b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/stress-v1_3_0-2026-05-20T14-12-01.json @@ -0,0 +1,19 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x7d61ec428cfae560f43647af567ea7c6e2cc5527", + "transactionHash": "0xd74da343a9f1079e3455d9d62396bb9b807a6b8e93e9727cf30c7f91adec5def", + "commit": "104c13df", + "timestamp": 1779286321, + "chainId": 11155111, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x708B3CA8b7Dc0f89Ea5a06709C3b92Dd5843B662", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "libraries": { + "src/ChannelEngine.sol:ChannelEngine": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407" + }, + "comment": "stress v1.3.0 with VALIDATOR_ACTIVATION_DELAY = 1 minute" +} diff --git a/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/2026-04-20T09-13-24.json b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/2026-04-20T09-13-24.json new file mode 100644 index 000000000..c50340fe3 --- /dev/null +++ b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/2026-04-20T09-13-24.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x165fe1a7d70DcF7594442CA9E2A572aA43b79E07", + "transactionHash": "0x00dd759837ba610d871720c093e6ae25a01dae1aae2061a800dab828df2b1868", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776676404, + "chainId": 11155111, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-45-36.json b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-45-36.json new file mode 100644 index 000000000..b1077e22d --- /dev/null +++ b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-45-36.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "transactionHash": "0x581be27b4d4b12595edd08dd50e3918e120bc78d2608cc359f85584d7896c576", + "commit": "b88d511c", + "timestamp": 1779648336, + "chainId": 11155111, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/stress-v1_3_0-2026-05-19T12-43-13.json b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/stress-v1_3_0-2026-05-19T12-43-13.json new file mode 100644 index 000000000..eeb6f6b63 --- /dev/null +++ b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/stress-v1_3_0-2026-05-19T12-43-13.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x708b3ca8b7dc0f89ea5a06709c3b92dd5843b662", + "transactionHash": "0x2ed88fa1af9f0513507fc508bdb9867d337657992a629fe93703318d56960a54", + "commit": "df4e110a", + "timestamp": 1779194593, + "chainId": 11155111, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "stress v1.3.0" +} diff --git a/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/2026-04-20T09-12-12.json b/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/2026-04-20T09-12-12.json new file mode 100644 index 000000000..f57d8edc5 --- /dev/null +++ b/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/2026-04-20T09-12-12.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xb5c2e8bad7417e654e9087753ec1d08762a06f91", + "transactionHash": "0x50bb959bdeced7036ec444f2eab254a07d956e3de99f0a92374bbc03d6fd4f73", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776676332, + "chainId": 11155111, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/dev-v1_3_0-2026-05-20T12-00-01.json b/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/dev-v1_3_0-2026-05-20T12-00-01.json new file mode 100644 index 000000000..01742f176 --- /dev/null +++ b/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/dev-v1_3_0-2026-05-20T12-00-01.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0xce9fcc6a0668dbe2025b191360ecb94da8e3d672ae3c1838f7ba3c85c355c3d5", + "commit": "9110ba06", + "timestamp": 1779278401, + "chainId": 11155111, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "stress and sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-04-20T09-12-12.json b/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-04-20T09-12-12.json new file mode 100644 index 000000000..64bd9090b --- /dev/null +++ b/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-04-20T09-12-12.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xaf9F833141094083B9de2cb6AA0f7E1f2d2DEee1", + "transactionHash": "0xd6b9ed855001301cb8227b179167e3296f30f5833d59845002ff6b7d320b2f82", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776676332, + "chainId": 11155111, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/dev-v1_3_0-2026-05-20T12-00-01.json b/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/dev-v1_3_0-2026-05-20T12-00-01.json new file mode 100644 index 000000000..c9ea73d0d --- /dev/null +++ b/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/dev-v1_3_0-2026-05-20T12-00-01.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0xa3cd407e3e4288bf59982197df01a2ddfcf2bc91d399a8c5eb1438ee3c825091", + "commit": "9110ba06", + "timestamp": 1779278401, + "chainId": 11155111, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "stress and sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T15-00-01.json b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T15-00-01.json new file mode 100644 index 000000000..a7660184b --- /dev/null +++ b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T15-00-01.json @@ -0,0 +1,17 @@ +{ + "deployer": "0x00C3Be935031A977eC5c8f2E3B2C4d67810Eaf9d", + "deployedTo": "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "transactionHash": "0xe656b40ff7cc5de2e8e929377fae23c379a64f64b0984736f9d77d5b754475e2", + "commit": "19288523ff4f7628f1517c4ac24f231b252a5400", + "timestamp": 1779375601, + "chainId": 11155111, + "contractPath": "src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "yusd", + "6", + "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "1000000000000000000" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/2026-04-20T09-01-49.json b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/2026-04-20T09-01-49.json new file mode 100644 index 000000000..cd1800444 --- /dev/null +++ b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/2026-04-20T09-01-49.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x36E4A80f54612497647539C8Ff8bDc1611Cb8f53", + "transactionHash": "0x9f801dd58f8763631628031700d010c16293bec8cb0852829d66787578ffc4f2", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776675709, + "chainId": 11155111, + "contractPath": "./src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "" +} diff --git a/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-49-13.json b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-49-13.json new file mode 100644 index 000000000..8db502003 --- /dev/null +++ b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-49-13.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3C0563D7a197037d1bc599Ae6Ab8892438F16353", + "deployedTo": "0xB08901fb3c9952f9e33469169A1E3290FaD79e22", + "transactionHash": "0x4b232ef89308ef1383d7eddb9ec3ae3de1343ceef79df4d51de1b125d36e7a06", + "commit": "b88d511c04580d662176e3900f141a8294259a56", + "timestamp": 1779648553, + "chainId": 11155111, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/stress-v1_3_0-2026-05-19T12-51-48.json b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/stress-v1_3_0-2026-05-19T12-51-48.json new file mode 100644 index 000000000..f7ab3f405 --- /dev/null +++ b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/stress-v1_3_0-2026-05-19T12-51-48.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0xe2B33Aa3922d7ac386e6801Ae7D9498C4DF45F1f", + "transactionHash": "0x7d8ed6acc062b28137ac08e8d1a0ed3368f39b2bf89f18910fec81ae77dffb55", + "commit": "df4e110a38ca3a4b17e46b606cc3233bbb7521c2", + "timestamp": 1779195108, + "chainId": 11155111, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "stress v1.3.0" +} diff --git a/contracts/deployments/137/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T11-59-40.json b/contracts/deployments/137/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T11-59-40.json new file mode 100644 index 000000000..b3887fb28 --- /dev/null +++ b/contracts/deployments/137/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T11-59-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x0d88bafd5e5255886a9c1ec51f6d7229eab4a04a250843ecf817066096095c91", + "commit": "e07ad9c2", + "timestamp": 1779451180, + "chainId": 137, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/137/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T11-59-40.json b/contracts/deployments/137/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T11-59-40.json new file mode 100644 index 000000000..8c43c8547 --- /dev/null +++ b/contracts/deployments/137/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T11-59-40.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0xbb12f834318e4a6b72687803a767c8e59f2b775440ffec98bf83dce637b8ac43", + "commit": "e07ad9c2", + "timestamp": 1779451180, + "chainId": 137, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/137/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T11-59-40.json b/contracts/deployments/137/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T11-59-40.json new file mode 100644 index 000000000..b33ba64f1 --- /dev/null +++ b/contracts/deployments/137/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T11-59-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0xa0122f2094082cf35e9f58575da84f78368c10de1366fc1f187314fe18520d8e", + "commit": "e07ad9c2", + "timestamp": 1779451180, + "chainId": 137, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/137/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T11-59-40.json b/contracts/deployments/137/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T11-59-40.json new file mode 100644 index 000000000..2caf2214f --- /dev/null +++ b/contracts/deployments/137/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T11-59-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0xd26256858ac3d4e23e13c8b4ad5e49d881628b3a67785ae8454664572cb2c2ed", + "commit": "e07ad9c2", + "timestamp": 1779451180, + "chainId": 137, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/137/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T11-59-40.json b/contracts/deployments/137/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T11-59-40.json new file mode 100644 index 000000000..9b8304ac3 --- /dev/null +++ b/contracts/deployments/137/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T11-59-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0xd49a4f0525a0b3d8748fe302f28f4766746a36fa0e1dcf8619a3452f38ee3eca", + "commit": "e07ad9c2", + "timestamp": 1779451180, + "chainId": 137, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/137/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-01-12.json b/contracts/deployments/137/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-01-12.json new file mode 100644 index 000000000..a1c7453f2 --- /dev/null +++ b/contracts/deployments/137/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-01-12.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x6a5144b9fc0b9b0cd95e5962f4467aff2d0fe481d8a1ec7d28e943ca75f3e0ae", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779451272, + "chainId": 137, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/14/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-24-31.json b/contracts/deployments/14/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-24-31.json new file mode 100644 index 000000000..78799a632 --- /dev/null +++ b/contracts/deployments/14/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-24-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x803f6d76e2293f03d2c2cd6f547df507828b4a71ea63d8706c930ff8840b32eb", + "commit": "e07ad9c2", + "timestamp": 1779452671, + "chainId": 14, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/14/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-24-31.json b/contracts/deployments/14/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-24-31.json new file mode 100644 index 000000000..cbe4f73ba --- /dev/null +++ b/contracts/deployments/14/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-24-31.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0xdcf31cd7c01c6a548c7a0a02171ab7694386b1a489c206133b63f24d87420a75", + "commit": "e07ad9c2", + "timestamp": 1779452671, + "chainId": 14, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/14/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-24-31.json b/contracts/deployments/14/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-24-31.json new file mode 100644 index 000000000..c807bd03f --- /dev/null +++ b/contracts/deployments/14/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-24-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0x3ae69da40a8649d359ab7d4adafbdfc9d86cd32be2a534ac58f1bd9675a96363", + "commit": "e07ad9c2", + "timestamp": 1779452671, + "chainId": 14, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/14/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-24-31.json b/contracts/deployments/14/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-24-31.json new file mode 100644 index 000000000..109b2c946 --- /dev/null +++ b/contracts/deployments/14/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-24-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x70e5c4cc39b9397e2c6181f43b7b15e3d8bfec7ccb3b1d86e12ba3cabe082b33", + "commit": "e07ad9c2", + "timestamp": 1779452671, + "chainId": 14, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/14/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-24-31.json b/contracts/deployments/14/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-24-31.json new file mode 100644 index 000000000..2c27c09f9 --- /dev/null +++ b/contracts/deployments/14/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-24-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0xdd4ea848360946f4a0699a2bbc85fc1886164f6de8d213cfecc818934e653772", + "commit": "e07ad9c2", + "timestamp": 1779452671, + "chainId": 14, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/14/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-25-44.json b/contracts/deployments/14/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-25-44.json new file mode 100644 index 000000000..cb553a1b9 --- /dev/null +++ b/contracts/deployments/14/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-25-44.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x4620f5f50fd79f1dfda5786921127b662bd718f5a7221a10e8a4b85e1a5f3eed", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779452744, + "chainId": 14, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1440000/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-20-59.json b/contracts/deployments/1440000/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-20-59.json new file mode 100644 index 000000000..d40aceaab --- /dev/null +++ b/contracts/deployments/1440000/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-20-59.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x6c9e41141b79c21edd45d64678f31b6f457672b7b8206047f6e46545294a816f", + "commit": "e07ad9c2", + "timestamp": 1779452459, + "chainId": 1440000, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1440000/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-20-59.json b/contracts/deployments/1440000/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-20-59.json new file mode 100644 index 000000000..1bff16ea7 --- /dev/null +++ b/contracts/deployments/1440000/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-20-59.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0x5013e736d9939604d954d9a43569e120c08e2eff473a29d655a22ec637845cef", + "commit": "e07ad9c2", + "timestamp": 1779452459, + "chainId": 1440000, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1440000/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-20-59.json b/contracts/deployments/1440000/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-20-59.json new file mode 100644 index 000000000..14a64f2ca --- /dev/null +++ b/contracts/deployments/1440000/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-20-59.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0x25d519a7a16430b5c2ef8df85a04aa37184aa45257bdcff4130f4ef2d09a7123", + "commit": "e07ad9c2", + "timestamp": 1779452459, + "chainId": 1440000, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1440000/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-20-59.json b/contracts/deployments/1440000/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-20-59.json new file mode 100644 index 000000000..a4c7c4afd --- /dev/null +++ b/contracts/deployments/1440000/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-20-59.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x2577f78efe05b0f1eb6019a07828785182877992b904d5c2d80b278b5903d8d3", + "commit": "e07ad9c2", + "timestamp": 1779452459, + "chainId": 1440000, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1440000/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-20-59.json b/contracts/deployments/1440000/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-20-59.json new file mode 100644 index 000000000..88de45bc3 --- /dev/null +++ b/contracts/deployments/1440000/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-20-59.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0xa22adafb38b1e88a5c00273904fc15eeeea3c96d3f796a193f30ad8169f02da5", + "commit": "e07ad9c2", + "timestamp": 1779452459, + "chainId": 1440000, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1440000/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-22-18.json b/contracts/deployments/1440000/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-22-18.json new file mode 100644 index 000000000..1f5e4f705 --- /dev/null +++ b/contracts/deployments/1440000/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-22-18.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x1076e0a3d56cd639c089e0c3a97c0750e4ee05268f104b9a4be2ffc3ec5d263f", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779452538, + "chainId": 1440000, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/1449000/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json b/contracts/deployments/1449000/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json new file mode 100644 index 000000000..9315a3c8f --- /dev/null +++ b/contracts/deployments/1449000/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0xdc22db9c5f7952782b70276b78a7fb5f51ba60edecac324f578ab2eae54ef578", + "commit": "a5cb25e2", + "timestamp": 1779364442, + "chainId": 1449000, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/1449000/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-39-27.json b/contracts/deployments/1449000/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-39-27.json new file mode 100644 index 000000000..53821b997 --- /dev/null +++ b/contracts/deployments/1449000/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-39-27.json @@ -0,0 +1,14 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "transactionHash": "0xaa23e4554906462d2056520d2e518a5605e1651dbd2c1aa7914adc9a140090b7", + "commit": "b88d511c", + "timestamp": 1779647967, + "chainId": 1449000, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/1449000/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-39-27.json b/contracts/deployments/1449000/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-39-27.json new file mode 100644 index 000000000..a3f1b1653 --- /dev/null +++ b/contracts/deployments/1449000/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-39-27.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "transactionHash": "0x1d4eed14867460fc5680093662a6525c6762d9f9c7d690fc26980a43fdaff9e8", + "commit": "b88d511c", + "timestamp": 1779647967, + "chainId": 1449000, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/1449000/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json b/contracts/deployments/1449000/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json new file mode 100644 index 000000000..31cba475e --- /dev/null +++ b/contracts/deployments/1449000/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0xa3f46d8e4cfdd400280eb8c17023c2355501c646269de13b1f2308629045ae3d", + "commit": "a5cb25e2", + "timestamp": 1779364442, + "chainId": 1449000, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/1449000/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json b/contracts/deployments/1449000/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json new file mode 100644 index 000000000..f80554cef --- /dev/null +++ b/contracts/deployments/1449000/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T11-54-02.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0xb96a14ffbb8eeea30e92b20e03926112892748a5ea99e293dba75e850a372192", + "commit": "a5cb25e2", + "timestamp": 1779364442, + "chainId": 1449000, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/1449000/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-57-16.json b/contracts/deployments/1449000/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-57-16.json new file mode 100644 index 000000000..253081bb6 --- /dev/null +++ b/contracts/deployments/1449000/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-57-16.json @@ -0,0 +1,17 @@ +{ + "deployer": "0x00C3Be935031A977eC5c8f2E3B2C4d67810Eaf9d", + "deployedTo": "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "transactionHash": "0xd23541f1949b625a66bc7f7f44554ca9c6931ccc8e54e20d7984dfc9039c99e0", + "commit": "19288523ff4f7628f1517c4ac24f231b252a5400", + "timestamp": 1779375436, + "chainId": 1449000, + "contractPath": "src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "yusd", + "18", + "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "1000000000000000000000000000000" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/1449000/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-42-52.json b/contracts/deployments/1449000/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-42-52.json new file mode 100644 index 000000000..e5c6fa833 --- /dev/null +++ b/contracts/deployments/1449000/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-42-52.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3C0563D7a197037d1bc599Ae6Ab8892438F16353", + "deployedTo": "0xB08901fb3c9952f9e33469169A1E3290FaD79e22", + "transactionHash": "0x76d8b9b6daecc85258da993ed58090eb4916ea4474a5470647cbd641e75edf43", + "commit": "b88d511c04580d662176e3900f141a8294259a56", + "timestamp": 1779648172, + "chainId": 1449000, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/480/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-49-52.json b/contracts/deployments/480/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-49-52.json new file mode 100644 index 000000000..3b70dda67 --- /dev/null +++ b/contracts/deployments/480/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-49-52.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0xf596d5d74c2b19cacffb933b4edc5133cf81af5c369864e33f80e0959362915a", + "commit": "e07ad9c2", + "timestamp": 1779454192, + "chainId": 480, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/480/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-49-52.json b/contracts/deployments/480/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-49-52.json new file mode 100644 index 000000000..1ec9e250f --- /dev/null +++ b/contracts/deployments/480/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-49-52.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0xa681431f362b46fba7e2416205ad0116316a13c88eaba206fe3d2f32aa630240", + "commit": "e07ad9c2", + "timestamp": 1779454192, + "chainId": 480, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/480/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-49-52.json b/contracts/deployments/480/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-49-52.json new file mode 100644 index 000000000..fafe1de93 --- /dev/null +++ b/contracts/deployments/480/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-49-52.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0x667c15364a70941a6914df17d30e509f9e1d347b1f35bffd353d89fc8b20ca02", + "commit": "e07ad9c2", + "timestamp": 1779454192, + "chainId": 480, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/480/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-49-52.json b/contracts/deployments/480/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-49-52.json new file mode 100644 index 000000000..6bdac0a88 --- /dev/null +++ b/contracts/deployments/480/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-49-52.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x03d5005e4380e60dde490cb28652ce8cb5894edd7c1bdd5e84d21fa6ed4db90e", + "commit": "e07ad9c2", + "timestamp": 1779454192, + "chainId": 480, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/480/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-49-52.json b/contracts/deployments/480/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-49-52.json new file mode 100644 index 000000000..36900b76d --- /dev/null +++ b/contracts/deployments/480/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-49-52.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x16796168009fc482674462e80b9a8985b6ebcdcc53ce8a9d9290ac04dd4eb07e", + "commit": "e07ad9c2", + "timestamp": 1779454192, + "chainId": 480, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/480/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-50-44.json b/contracts/deployments/480/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-50-44.json new file mode 100644 index 000000000..2f8d6ca65 --- /dev/null +++ b/contracts/deployments/480/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-50-44.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x4dff47c8d08dfcef96b103f0c97cc542e70136fda435324194accb237884382c", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779454244, + "chainId": 480, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/56/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-03-31.json b/contracts/deployments/56/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-03-31.json new file mode 100644 index 000000000..396fad0b7 --- /dev/null +++ b/contracts/deployments/56/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-03-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x482709d9867b6ccb857df938deff48bb201a2e759b9c2323fbdaea2973e49c2f", + "commit": "e07ad9c2", + "timestamp": 1779451411, + "chainId": 56, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/56/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-03-31.json b/contracts/deployments/56/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-03-31.json new file mode 100644 index 000000000..fe814ef13 --- /dev/null +++ b/contracts/deployments/56/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-03-31.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0x639841bde9847da0980121aa4a94ce88f830b446c9e911bc3f48fbd232ea7aa1", + "commit": "e07ad9c2", + "timestamp": 1779451411, + "chainId": 56, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/56/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-03-31.json b/contracts/deployments/56/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-03-31.json new file mode 100644 index 000000000..586c2eb0f --- /dev/null +++ b/contracts/deployments/56/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-03-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0x5b0f4c1e0255284701b924d2782bf997342da5fdfd90bd000f9ff92306712a8e", + "commit": "e07ad9c2", + "timestamp": 1779451411, + "chainId": 56, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/56/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-03-31.json b/contracts/deployments/56/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-03-31.json new file mode 100644 index 000000000..5b3a6ed11 --- /dev/null +++ b/contracts/deployments/56/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-03-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0xabacfdc37a4fdc8b2d4dea6e64e810eae73518ac51abe4eaa55c0ef2f93b9ca6", + "commit": "e07ad9c2", + "timestamp": 1779451411, + "chainId": 56, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/56/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-03-31.json b/contracts/deployments/56/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-03-31.json new file mode 100644 index 000000000..8f4a843e7 --- /dev/null +++ b/contracts/deployments/56/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-03-31.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x985e1048fe3359fbdbb78845ad836051538a2efc8fe16516debfc2cb7fbd88cb", + "commit": "e07ad9c2", + "timestamp": 1779451411, + "chainId": 56, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/56/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-04-38.json b/contracts/deployments/56/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-04-38.json new file mode 100644 index 000000000..9d6436a84 --- /dev/null +++ b/contracts/deployments/56/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-04-38.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x1db1bbf1d0a5bfdf38bedc6667cd0aa71a44f5d26e4846e6219c802258659fd5", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779451478, + "chainId": 56, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/59141/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json b/contracts/deployments/59141/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json new file mode 100644 index 000000000..fc8d9a5b4 --- /dev/null +++ b/contracts/deployments/59141/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x2ac9ccb2d6a7a9f0f6aaaa515588a8dd3ffbd87f6965dac4280a0bd7826c5f24", + "commit": "76cc8d2c", + "timestamp": 1779363212, + "chainId": 59141, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59141/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-22-11.json b/contracts/deployments/59141/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-22-11.json new file mode 100644 index 000000000..20d532aac --- /dev/null +++ b/contracts/deployments/59141/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-22-11.json @@ -0,0 +1,14 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "transactionHash": "0x6392cece24358fce8adc14e780f6d34a32ac6c3d699dbf8260edc71432d77327", + "commit": "b88d511c", + "timestamp": 1779646931, + "chainId": 59141, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59141/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-22-11.json b/contracts/deployments/59141/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-22-11.json new file mode 100644 index 000000000..4a14777d3 --- /dev/null +++ b/contracts/deployments/59141/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-22-11.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "transactionHash": "0x247f3062d94171b11d634431a1168f6d0add702f08cf5e52acb8ed0642ea7f41", + "commit": "b88d511c", + "timestamp": 1779646931, + "chainId": 59141, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59141/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json b/contracts/deployments/59141/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json new file mode 100644 index 000000000..68a1dee1b --- /dev/null +++ b/contracts/deployments/59141/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x8590922c6571c3e212736e1c88cd82d2a38a13c4026fa71ecd331ffd288dd295", + "commit": "76cc8d2c", + "timestamp": 1779363212, + "chainId": 59141, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59141/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json b/contracts/deployments/59141/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json new file mode 100644 index 000000000..390b3b73d --- /dev/null +++ b/contracts/deployments/59141/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T11-33-32.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x8a149aaf4be2aa8f0d2ebae69ab29ede62e20494891a5a287e6fa7c86397f079", + "commit": "76cc8d2c", + "timestamp": 1779363212, + "chainId": 59141, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59141/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-58-01.json b/contracts/deployments/59141/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-58-01.json new file mode 100644 index 000000000..fd052db6a --- /dev/null +++ b/contracts/deployments/59141/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-58-01.json @@ -0,0 +1,17 @@ +{ + "deployer": "0x00C3Be935031A977eC5c8f2E3B2C4d67810Eaf9d", + "deployedTo": "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "transactionHash": "0x3f900d8e2ca654e8a6e8661db9675420b4be0e093f983a6b21e4676a1a89df34", + "commit": "19288523ff4f7628f1517c4ac24f231b252a5400", + "timestamp": 1779375481, + "chainId": 59141, + "contractPath": "src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "yusd", + "18", + "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "1000000000000000000000000000000" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59141/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-25-10.json b/contracts/deployments/59141/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-25-10.json new file mode 100644 index 000000000..d497eae84 --- /dev/null +++ b/contracts/deployments/59141/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-25-10.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3C0563D7a197037d1bc599Ae6Ab8892438F16353", + "deployedTo": "0xB08901fb3c9952f9e33469169A1E3290FaD79e22", + "transactionHash": "0xa0ae3e8dd47fb1095c731bbd0817314ad29d43bc7ee6fc0f9564462ecd0398a6", + "commit": "b88d511c04580d662176e3900f141a8294259a56", + "timestamp": 1779647110, + "chainId": 59141, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/59144/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-12-40.json b/contracts/deployments/59144/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-12-40.json new file mode 100644 index 000000000..a21170797 --- /dev/null +++ b/contracts/deployments/59144/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-12-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x46748d521e948da676b5a4a49580316375b512fe356e03740d87f1321db3ccf8", + "commit": "e07ad9c2", + "timestamp": 1779451960, + "chainId": 59144, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/59144/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-12-40.json b/contracts/deployments/59144/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-12-40.json new file mode 100644 index 000000000..255666bf4 --- /dev/null +++ b/contracts/deployments/59144/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-12-40.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0xa91c6de70e8b9e6f9b943d4a2a1c9895ae816d1aa1dbc98a3f7e48d35c5558f0", + "commit": "e07ad9c2", + "timestamp": 1779451960, + "chainId": 59144, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/59144/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-12-40.json b/contracts/deployments/59144/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-12-40.json new file mode 100644 index 000000000..f757ffb91 --- /dev/null +++ b/contracts/deployments/59144/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-12-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0xd6285f3667e6ef5c98f210583e3fee6f67ed755f8a70cd2de58c4732a7421a94", + "commit": "e07ad9c2", + "timestamp": 1779451960, + "chainId": 59144, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/59144/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-12-40.json b/contracts/deployments/59144/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-12-40.json new file mode 100644 index 000000000..d50fd9b9e --- /dev/null +++ b/contracts/deployments/59144/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-12-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0xcc6b8bb892df220c68efa59cb79544edccd437f1d55ae84900f6e0000558c13d", + "commit": "e07ad9c2", + "timestamp": 1779451960, + "chainId": 59144, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/59144/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-12-40.json b/contracts/deployments/59144/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-12-40.json new file mode 100644 index 000000000..0d12d81b1 --- /dev/null +++ b/contracts/deployments/59144/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-12-40.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x8d06af1943f08a1f9c444fca28eaeb0bcdccad331a5049c69f0ea216b10fb2e1", + "commit": "e07ad9c2", + "timestamp": 1779451960, + "chainId": 59144, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/59144/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-14-17.json b/contracts/deployments/59144/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-14-17.json new file mode 100644 index 000000000..c1d317061 --- /dev/null +++ b/contracts/deployments/59144/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-14-17.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x89011eaf2a6b216230239b488c5efb19ea50abfd00e68993c8962ac99279ef14", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779452057, + "chainId": 59144, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/80002/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json b/contracts/deployments/80002/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json new file mode 100644 index 000000000..31f5ec806 --- /dev/null +++ b/contracts/deployments/80002/ChannelEngine.sol_ChannelEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0xcfcabc6f5e685e11cd0ee8916a495f171b5ce6dd1a803d3baf123b70d8d23f12", + "commit": "5922d170", + "timestamp": 1779355867, + "chainId": 80002, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-31-37.json b/contracts/deployments/80002/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-31-37.json new file mode 100644 index 000000000..f856f2f63 --- /dev/null +++ b/contracts/deployments/80002/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-31-37.json @@ -0,0 +1,14 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "transactionHash": "0x38c81109784b6c39f83d60eafef714286ab0299b1142a3e5b205d0a955dd4293", + "commit": "b88d511c", + "timestamp": 1779647497, + "chainId": 80002, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-31-37.json b/contracts/deployments/80002/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-31-37.json new file mode 100644 index 000000000..393364596 --- /dev/null +++ b/contracts/deployments/80002/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-31-37.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "transactionHash": "0xe618efbe18432847bb7cac30abe5cc277ce165401c9512c79552dbd12191bae1", + "commit": "b88d511c", + "timestamp": 1779647497, + "chainId": 80002, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json b/contracts/deployments/80002/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json new file mode 100644 index 000000000..9bc010017 --- /dev/null +++ b/contracts/deployments/80002/EscrowDepositEngine.sol_EscrowDepositEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x01f9e653982796b7b022ed0d475745c76904848e9de7dada1c2aa232974697a5", + "commit": "5922d170", + "timestamp": 1779355867, + "chainId": 80002, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json b/contracts/deployments/80002/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json new file mode 100644 index 000000000..f0fc907da --- /dev/null +++ b/contracts/deployments/80002/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/sandbox-v1_3_0-2026-05-21T09-31-07.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x81b8e83810f5c33fb9224e184defbae5197f688eb95124bff530f020af1f4c98", + "commit": "5922d170", + "timestamp": 1779355867, + "chainId": 80002, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T15-00-37.json b/contracts/deployments/80002/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T15-00-37.json new file mode 100644 index 000000000..0042cae7d --- /dev/null +++ b/contracts/deployments/80002/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T15-00-37.json @@ -0,0 +1,17 @@ +{ + "deployer": "0x00C3Be935031A977eC5c8f2E3B2C4d67810Eaf9d", + "deployedTo": "0x1b66e510B845a746334B866b62C64746bEbfF857", + "transactionHash": "0x4e653b2739486aa767d0786a0a1ffea66b695347daad7dbca382c775ecd6581f", + "commit": "19288523ff4f7628f1517c4ac24f231b252a5400", + "timestamp": 1779375637, + "chainId": 80002, + "contractPath": "src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "yusd", + "8", + "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "100000000000000000000" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-21T09-32-52.json b/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-21T09-32-52.json new file mode 100644 index 000000000..34959da39 --- /dev/null +++ b/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-21T09-32-52.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xbab179854bA39cfcFb2D1D98a05fCFA113CF4ac1", + "deployedTo": "0xC4C419e0a8D11BF007C7126dbbce6F633a0363D6", + "transactionHash": "0x2c624fde06d821d607f99058117176193ecdc2179a9b122041e725d364dfb736", + "commit": "5922d1707a10340148adb41414f956184c0c5c34", + "timestamp": 1779355972, + "chainId": 80002, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-33-34.json b/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-33-34.json new file mode 100644 index 000000000..f497a7cc4 --- /dev/null +++ b/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-33-34.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3C0563D7a197037d1bc599Ae6Ab8892438F16353", + "deployedTo": "0xB08901fb3c9952f9e33469169A1E3290FaD79e22", + "transactionHash": "0x1b6e679fc60cf983263fe8316e20d7ab514809269f025797ddb5c8ff4a837b77", + "commit": "b88d511c04580d662176e3900f141a8294259a56", + "timestamp": 1779647614, + "chainId": 80002, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/8453/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-45-55.json b/contracts/deployments/8453/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-45-55.json new file mode 100644 index 000000000..0e7f7780e --- /dev/null +++ b/contracts/deployments/8453/ChannelEngine.sol_ChannelEngine/prod-v1_3_0-2026-05-22T12-45-55.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0x7bfeb3c5ea216c7628d479e323352d9089b308b692fbc143757aeb39eaa177d8", + "commit": "e07ad9c2", + "timestamp": 1779453955, + "chainId": 8453, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/8453/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-45-55.json b/contracts/deployments/8453/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-45-55.json new file mode 100644 index 000000000..a747c6f7b --- /dev/null +++ b/contracts/deployments/8453/ChannelHub.sol_ChannelHub/prod-v1_3_0-2026-05-22T12-45-55.json @@ -0,0 +1,14 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1", + "transactionHash": "0x437caf63d9d3df4ecf493ac0645bcdac5304730848e9627f2ee0bb19a83d8511", + "commit": "e07ad9c2", + "timestamp": 1779453955, + "chainId": 8453, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x98de11a97fDc08D057FC9Ab310864655C49Bbf8E", + "0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6" + ], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/8453/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-45-55.json b/contracts/deployments/8453/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-45-55.json new file mode 100644 index 000000000..69b0a4774 --- /dev/null +++ b/contracts/deployments/8453/ECDSAValidator.sol_ECDSAValidator/prod-v1_3_0-2026-05-22T12-45-55.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0x98de11a97fdc08d057fc9ab310864655c49bbf8e", + "transactionHash": "0xcf1f6b75073fd56a8f258ec6cf3b511a05b653da99620518d192ecbfc5ef5bca", + "commit": "e07ad9c2", + "timestamp": 1779453955, + "chainId": 8453, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/8453/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-45-55.json b/contracts/deployments/8453/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-45-55.json new file mode 100644 index 000000000..486102c58 --- /dev/null +++ b/contracts/deployments/8453/EscrowDepositEngine.sol_EscrowDepositEngine/prod-v1_3_0-2026-05-22T12-45-55.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x815bd83c65c1c423b43476b69487acf8dd56adf60e05b0703dcedc86f0053716", + "commit": "e07ad9c2", + "timestamp": 1779453955, + "chainId": 8453, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/8453/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-45-55.json b/contracts/deployments/8453/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-45-55.json new file mode 100644 index 000000000..657c0e489 --- /dev/null +++ b/contracts/deployments/8453/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/prod-v1_3_0-2026-05-22T12-45-55.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xe6521da9b54740e5fe9e002eb7bbf86d5698e581", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x1e23a0663596972c73f7a984a70433dfd04def9d1eeaf99e9447ee56cbeffadd", + "commit": "e07ad9c2", + "timestamp": 1779453955, + "chainId": 8453, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/8453/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-47-17.json b/contracts/deployments/8453/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-47-17.json new file mode 100644 index 000000000..0273a98a9 --- /dev/null +++ b/contracts/deployments/8453/SessionKeyValidator.sol_SessionKeyValidator/prod-v1_3_0-2026-05-22T12-47-17.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xE6521DA9B54740E5Fe9e002eb7Bbf86d5698e581", + "deployedTo": "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC", + "transactionHash": "0x67ba9760580c6ac28a836d415d03b29c3240224cdbf6c6160acf572921d60cdb", + "commit": "e07ad9c2c335d7c2c3f2f91caf371af81e912f57", + "timestamp": 1779454037, + "chainId": 8453, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "prod v1.3.0" +} diff --git a/contracts/deployments/84532/ChannelEngine.sol_ChannelEngine/2026-04-20T09-46-56.json b/contracts/deployments/84532/ChannelEngine.sol_ChannelEngine/2026-04-20T09-46-56.json new file mode 100644 index 000000000..20d5f35e3 --- /dev/null +++ b/contracts/deployments/84532/ChannelEngine.sol_ChannelEngine/2026-04-20T09-46-56.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x353A207A7bC822D8d3E58bcA2f3F9E2b90B26e78", + "transactionHash": "0x7261a8f5f722c829e8d4381a519c2aa8c8fe689a7d5e1902494d7a74bd68426a", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776678416, + "chainId": 84532, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/84532/ChannelEngine.sol_ChannelEngine/dev-v1_3_0-2026-05-20T12-05-28.json b/contracts/deployments/84532/ChannelEngine.sol_ChannelEngine/dev-v1_3_0-2026-05-20T12-05-28.json new file mode 100644 index 000000000..c47e5b1a3 --- /dev/null +++ b/contracts/deployments/84532/ChannelEngine.sol_ChannelEngine/dev-v1_3_0-2026-05-20T12-05-28.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "transactionHash": "0xd382762780c6e0661cc1f7bb228f30f6e46461be9efacc3b54750dbe8d27dfa0", + "commit": "9110ba06", + "timestamp": 1779278728, + "chainId": 84532, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "stress and sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/ChannelHub.sol_ChannelHub/2026-04-20T09-46-56.json b/contracts/deployments/84532/ChannelHub.sol_ChannelHub/2026-04-20T09-46-56.json new file mode 100644 index 000000000..b1a3c74e8 --- /dev/null +++ b/contracts/deployments/84532/ChannelHub.sol_ChannelHub/2026-04-20T09-46-56.json @@ -0,0 +1,16 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x6E2C4707DA119425dF2c722E2695300154652f56", + "transactionHash": "0x470639b186a290226a188b8ee03cd26ddf38aedfdc2d09e269284f7cefdc2fcc", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776678416, + "chainId": 84532, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": ["0xCe87FD88F4B5Fd5475d163e2642C5c2c7dD655Ec", "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9"], + "libraries": { + "src/ChannelEngine.sol:ChannelEngine": "0x353A207A7bC822D8d3E58bcA2f3F9E2b90B26e78", + "src/EscrowDepositEngine.sol:EscrowDepositEngine": "0xb5c2e8BAD7417E654E9087753EC1D08762a06f91", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine": "0xaf9F833141094083B9de2cb6AA0f7E1f2d2DEee1" + }, + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/84532/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-34-55.json b/contracts/deployments/84532/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-34-55.json new file mode 100644 index 000000000..c40c8fdf6 --- /dev/null +++ b/contracts/deployments/84532/ChannelHub.sol_ChannelHub/sandbox-v1_3_0-2026-05-24T18-34-55.json @@ -0,0 +1,14 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0x5dba8515af063db0c243c15ece7b99f91459c7c3", + "transactionHash": "0x2446552603e20de6e253d0bb3919b0029013e28d8096341cc0a9639381848ab4", + "commit": "b88d511c", + "timestamp": 1779647695, + "chainId": 84532, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0xb1cDEA413a080b3c6Eb3D37e1C24D8CD10cE5844", + "0x6F375dAD1FF0aD968BDdE939F76a2B3D6B9D3eC5" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/ChannelHub.sol_ChannelHub/stress-v1_3_0-2026-05-20T12-05-28.json b/contracts/deployments/84532/ChannelHub.sol_ChannelHub/stress-v1_3_0-2026-05-20T12-05-28.json new file mode 100644 index 000000000..42c6d440f --- /dev/null +++ b/contracts/deployments/84532/ChannelHub.sol_ChannelHub/stress-v1_3_0-2026-05-20T12-05-28.json @@ -0,0 +1,19 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x61b9e0767f2eca7e33802e82f9c64b1ebe72ba31", + "transactionHash": "0xb63e91129e4742f33a2e81449b74ddbe783a3c87188f4b2a73062089cae9a37d", + "commit": "9110ba06", + "timestamp": 1779278728, + "chainId": 84532, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0xB5E7D2B8DB56A173Ca8c05CDdCC1379852CdC095", + "0x2B6dc5BB33F3eaAbfd3A8d17fDb7BdB8fEf331f9" + ], + "libraries": { + "src/ChannelEngine.sol:ChannelEngine": "0x89b81857a46cf290f23f6ff9b24e1031aad65204", + "src/EscrowDepositEngine.sol:EscrowDepositEngine": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407" + }, + "comment": "stress v1.3.0 with VALIDATOR_ACTIVATION_DELAY = 1 minute" +} diff --git a/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/2026-04-20T09-46-56.json b/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/2026-04-20T09-46-56.json new file mode 100644 index 000000000..29fc3c02d --- /dev/null +++ b/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/2026-04-20T09-46-56.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xCe87FD88F4B5Fd5475d163e2642C5c2c7dD655Ec", + "transactionHash": "0x3a7095c50cafe072b66d893f4583722b03a14aba4cc8d9a3b359b88135028f5c", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776678416, + "chainId": 84532, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-34-55.json b/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-34-55.json new file mode 100644 index 000000000..4f616740d --- /dev/null +++ b/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/sandbox-v1_3_0-2026-05-24T18-34-55.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3c0563d7a197037d1bc599ae6ab8892438f16353", + "deployedTo": "0xb1cdea413a080b3c6eb3d37e1c24d8cd10ce5844", + "transactionHash": "0xdc8081368cdddc4941975b8cffafe0e9c8bfb4ef350a597187d5ef7fd3297942", + "commit": "b88d511c", + "timestamp": 1779647695, + "chainId": 84532, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/stress-v1_3_0-2026-05-19T12-55-27.json b/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/stress-v1_3_0-2026-05-19T12-55-27.json new file mode 100644 index 000000000..7fe477483 --- /dev/null +++ b/contracts/deployments/84532/ECDSAValidator.sol_ECDSAValidator/stress-v1_3_0-2026-05-19T12-55-27.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xb5e7d2b8db56a173ca8c05cddcc1379852cdc095", + "transactionHash": "0x6569b61c32d2b85b249a9bb4d221d9312de3691351117c62598df3cb222863c8", + "commit": "df4e110a", + "timestamp": 1779195327, + "chainId": 84532, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "stress v1.3.0" +} diff --git a/contracts/deployments/84532/EscrowDepositEngine.sol_EscrowDepositEngine/2026-04-20T09-46-56.json b/contracts/deployments/84532/EscrowDepositEngine.sol_EscrowDepositEngine/2026-04-20T09-46-56.json new file mode 100644 index 000000000..a4c139d47 --- /dev/null +++ b/contracts/deployments/84532/EscrowDepositEngine.sol_EscrowDepositEngine/2026-04-20T09-46-56.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xb5c2e8BAD7417E654E9087753EC1D08762a06f91", + "transactionHash": "0x7bb8823f66bdb3f9bef019607214000a7dc0eb0148a523dbce2c6709d42e7a74", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776678416, + "chainId": 84532, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/84532/EscrowDepositEngine.sol_EscrowDepositEngine/dev-v1_3_0-2026-05-20T12-05-28.json b/contracts/deployments/84532/EscrowDepositEngine.sol_EscrowDepositEngine/dev-v1_3_0-2026-05-20T12-05-28.json new file mode 100644 index 000000000..658347f8b --- /dev/null +++ b/contracts/deployments/84532/EscrowDepositEngine.sol_EscrowDepositEngine/dev-v1_3_0-2026-05-20T12-05-28.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xdccc09e335b87fb506c40a972e76fc7a225e0bf9", + "transactionHash": "0x3c6043743ee8f39a6fe2b4b40e236755c2dd7da980d4fc5340e3e44fe4f949e4", + "commit": "9110ba06", + "timestamp": 1779278728, + "chainId": 84532, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "stress and sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-04-20T09-46-56.json b/contracts/deployments/84532/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-04-20T09-46-56.json new file mode 100644 index 000000000..54c740858 --- /dev/null +++ b/contracts/deployments/84532/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-04-20T09-46-56.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xaf9F833141094083B9de2cb6AA0f7E1f2d2DEee1", + "transactionHash": "0x93cf089b7e57207fad34690b6525d97b78d4452eadddc3dd979b28224528b89a", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776678416, + "chainId": 84532, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "clearnet-stress-v1.2.1" +} diff --git a/contracts/deployments/84532/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/dev-v1_3_0-2026-05-20T12-05-28.json b/contracts/deployments/84532/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/dev-v1_3_0-2026-05-20T12-05-28.json new file mode 100644 index 000000000..6526f6756 --- /dev/null +++ b/contracts/deployments/84532/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/dev-v1_3_0-2026-05-20T12-05-28.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xdca4ab495188b545cfa919c0cb0a7e2280f2f407", + "transactionHash": "0x3a642c4c020499885b2b289bfacabbf19b80a4165e7968109c0cc0536753ae7f", + "commit": "9110ba06", + "timestamp": 1779278728, + "chainId": 84532, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "stress and sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/PremintERC20.sol_PremintERC20/2026-04-20T09-59-36.json b/contracts/deployments/84532/PremintERC20.sol_PremintERC20/2026-04-20T09-59-36.json new file mode 100644 index 000000000..a4492a6a0 --- /dev/null +++ b/contracts/deployments/84532/PremintERC20.sol_PremintERC20/2026-04-20T09-59-36.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x96dE620f629efd76935070f040A9B3AC855339EB", + "transactionHash": "0xace25fc834f310e37acdafa40890713d710f0b60377019924782fa255ca3610a", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776679176, + "chainId": 84532, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "BNB", + "BNB", + "8", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "1000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/84532/PremintERC20.sol_PremintERC20/2026-04-20T10-00-15.json b/contracts/deployments/84532/PremintERC20.sol_PremintERC20/2026-04-20T10-00-15.json new file mode 100644 index 000000000..00674eda9 --- /dev/null +++ b/contracts/deployments/84532/PremintERC20.sol_PremintERC20/2026-04-20T10-00-15.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x541aa129B863bbd8A7176a1579b7558CACc06671", + "transactionHash": "0xbcdc5b4efb778d80c2a34e5d160a7c316c16fefeff9d890bc23438ab1e38ed18", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776679215, + "chainId": 84532, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "yusd", + "Yellow USD", + "8", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "1000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/84532/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-59-43.json b/contracts/deployments/84532/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-59-43.json new file mode 100644 index 000000000..70711ee5f --- /dev/null +++ b/contracts/deployments/84532/PremintERC20.sol_PremintERC20/sandbox-v1_3_0-2026-05-21T14-59-43.json @@ -0,0 +1,17 @@ +{ + "deployer": "0x00C3Be935031A977eC5c8f2E3B2C4d67810Eaf9d", + "deployedTo": "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde", + "transactionHash": "0xea51f7339ef94756c4b68db00645f771d375a495f66b123e9dfe89c8cb764f48", + "commit": "19288523ff4f7628f1517c4ac24f231b252a5400", + "timestamp": 1779375583, + "chainId": 84532, + "contractPath": "src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "yusd", + "6", + "0xbab179854ba39cfcfb2d1d98a05fcfa113cf4ac1", + "1000000000000000000" + ], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/2026-04-20T09-48-16.json b/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/2026-04-20T09-48-16.json new file mode 100644 index 000000000..e09ff7ea4 --- /dev/null +++ b/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/2026-04-20T09-48-16.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5", + "transactionHash": "0x6fd881eb27dc64f91613f1e464647b38c61a236c0857b1036b0349d1cb5be918", + "commit": "6c0a41d5b08eb6356fd7391536ede1db8d0b6a73", + "timestamp": 1776678496, + "chainId": 84532, + "contractPath": "./src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "" +} diff --git a/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-36-03.json b/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-36-03.json new file mode 100644 index 000000000..bd85ed27f --- /dev/null +++ b/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/sandbox-v1_3_0-2026-05-24T18-36-03.json @@ -0,0 +1,11 @@ +{ + "deployer": "0x3C0563D7a197037d1bc599Ae6Ab8892438F16353", + "deployedTo": "0xB08901fb3c9952f9e33469169A1E3290FaD79e22", + "transactionHash": "0xfc6353a05f3012ceb7ce6393a6af262cd1723fc03e5718af933a8051a7a35749", + "commit": "b88d511c04580d662176e3900f141a8294259a56", + "timestamp": 1779647763, + "chainId": 84532, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "sandbox v1.3.0" +} diff --git a/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/stress-v1_3_0-2026-05-19T12-58-32.json b/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/stress-v1_3_0-2026-05-19T12-58-32.json new file mode 100644 index 000000000..e286b3566 --- /dev/null +++ b/contracts/deployments/84532/SessionKeyValidator.sol_SessionKeyValidator/stress-v1_3_0-2026-05-19T12-58-32.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x4085554a56F962b6c8eeeb017Bf2e9D2F3E31131", + "transactionHash": "0x287a20fc93f8a9448e8f7d290471cdb84d38107d73a68e25635a91884775c4a9", + "commit": "df4e110a38ca3a4b17e46b606cc3233bbb7521c2", + "timestamp": 1779195512, + "chainId": 84532, + "contractPath": "src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "stress v1.3.0" +} diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 7dfca72f5..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 = 2_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 = 2_000 } + { paths = "src/ChannelHub.sol", optimizer_runs = 750 } ] diff --git a/contracts/initial-user-sig-validation.md b/contracts/initial-user-sig-validation.md new file mode 100644 index 000000000..82482dc97 --- /dev/null +++ b/contracts/initial-user-sig-validation.md @@ -0,0 +1,482 @@ +# Initial User Signature Validation: Approaches Comparison + +## Background: The Vulnerability + +`createChannel()` accepts a `ChannelDefinition` from calldata and passes +`def.approvedSignatureValidators` into `_validateSignatures()`. That function calls +`_extractValidator()`, which selects the validator based on the first byte of `userSig` +and only checks that the chosen `validatorId` is set in `approvedSignatureValidators` +before invoking it. + +A node can register a malicious `ISignatureValidator` via `registerNodeValidator()` that +always returns `VALIDATION_SUCCESS`, then set `approvedSignatureValidators` in calldata to +include that validator. `createChannel()` then succeeds with an arbitrary `userSig` — +the channel is created without user consent. + +The same bypass applies to `closeChannel()`, enabling the node to close the channel and +push locked funds to itself. + +**Root cause — circular dependency:** + +```txt +approvedSignatureValidators (supplied by attacker in calldata) + → selects which validator to verify user's sig + → verifies user's "approval" of approvedSignatureValidators +``` + +The protocol description notes that `approvedSignatureValidators` being part of `channelId` +prevents retroactive validator swapping on existing channels. It does nothing to prevent a +node from crafting a fresh `ChannelDefinition` with a malicious validator for a brand-new +channel, because there is no pre-existing user signature to protect. + +--- + +## Constraints on the Fix + +1. **No additional user interaction.** The existing interaction may change format, but an + extra signing step or extra on-chain transaction is not acceptable. +2. **Session key compatibility.** Session keys allow users to pre-sign states off-chain so + they are not required online per operation. Any fix must preserve this: the user should + not be required to submit `createChannel` themselves. +3. **Relayer model.** Nodes or relayers may call `createChannel` on behalf of the user + (e.g., to enforce a state when the user is offline). +4. **ERC-4337 (smart wallet) support — including revocability.** ERC-4337 wallets can + rotate keys, which revokes historical signatures. A signature valid at signing time may + fail `IERC1271(def.user).isValidSignature()` by the time `createChannel` is submitted. + The fix must not silently break ERC-4337 users. +5. **Extensible without ChannelHub redeployment.** New wallet formats (ERC-4337, future + schemes) must be addable without requiring users to migrate to a new deployment. +6. **No new trust on calldata-controlled data.** The validator used to verify user consent + must not be derivable from attacker-controlled calldata. + +--- + +## Cross-Cutting Concerns + +### Session Keys and `createChannel` + +Session keys are node-registered validators (e.g., id = 5 in the node's registry). Any fix +that restricts `createChannel` user-sig validation to a separate, node-independent set will +make session keys **unusable for `createChannel` specifically**. + +This is acceptable: the user must sign the initial `createChannel` state with a +bootstrap-compatible method (ECDSA or a protocol-approved validator). Session keys continue +to work for every subsequent on-chain operation (`depositToChannel`, `withdrawFromChannel`, +`checkpointChannel`, etc.), where `approvedSignatureValidators` is used normally. + +The off-chain protocol must produce both: + +- a session-key-signed version of each state (for off-chain use and subsequent on-chain ops) +- a bootstrap-compatible signature for any state that may be submitted to `createChannel` + +This is one additional signature format for the initial state, not an additional round trip. + +### ERC-4337 + Signature Revocability + Freezer Contract + +ERC-4337 wallets implement `IERC1271` but may rotate their signing keys, revoking historical +signatures. Calling `IERC1271(def.user).isValidSignature(hash, sig)` at `createChannel` time +will fail if the wallet has rotated its key since the state was signed. + +The solution is a **signature freezer contract**: + +1. While the signature is still valid, the user (or relayer) calls + `freezer.freeze(channelId, signingData, userSig)`. +2. The freezer validates `IERC1271(def.user).isValidSignature(hash, userSig)` and records + the result: `frozenSigs[keccak256(hash, userSig, def.user)] = true`. +3. `createChannel` is called; the bootstrap validator checks `freezer.isFrozen(...)`. +4. Even if the wallet later rotates keys, the frozen record is permanent. + +**Critical constraint:** the freezer contract address must NOT come from calldata. If it did, +an attacker would embed a malicious always-true freezer — reintroducing the original +vulnerability. The freezer address must be a hardcoded or governance-approved reference +inside the bootstrap validator contract itself. + +--- + +## Options + +### Option A — Hardcoded Immutable Bootstrap Validator Set *(NOT VIABLE)* + +A fixed list of validators is hardcoded in the contract (or set at deploy time and never +changed). For `createChannel` user sigs, only these validators may be used. + +Example initial set: + +- id = 0: Default ECDSA / EIP-191 (EOAs) +- id = 1: ERC-1271 validator (calls `IERC1271(def.user).isValidSignature`) + +**Pros:** + +- Simple and fully auditable. +- No governance or admin trust required. + +**Cons:** + +- **Not extensible without redeployment.** If ERC-4337 support (freezer validator) is not + included at launch, adding it requires migrating all users to a new ChannelHub. +- ERC-1271 branch has revocability issue for ERC-4337 (see above). +- Any future wallet format not covered at launch requires redeployment. + +--- + +### Option B — Universal Bootstrap Validator (immutable, ECDSA + ERC-1271 + ERC-6492) *(NOT VIABLE)* + +A single validator contract, referenced by an `immutable` in ChannelHub, that internally +handles multiple signature formats: + +- Raw ECDSA / EIP-191 (for EOAs) +- ERC-1271: calls `IERC1271(def.user).isValidSignature(hash, sig)` +- ERC-6492: for counterfactual smart wallets — deploys the wallet, then calls ERC-1271 + +For `createChannel`, the user sig is always validated through this one validator; +`approvedSignatureValidators` plays no role. + +**Why ERC-1271 does NOT reintroduce the original vulnerability:** + +The original attack works because the validator is selected from a node-controlled registry. +For ERC-1271, the call target is `def.user` — the victim's own address. The attacker +controls the `sig` bytes but cannot influence the code at `def.user`. For a real victim: + +- EOA (no code): the ERC-1271 call reverts → validation fails. +- Smart wallet (deployed code): the call goes to the wallet's own logic, which only accepts + signatures that genuinely satisfy its security model. + +An attacker cannot inject a malicious contract through the signature bytes, because the +target is fixed to `def.user`. + +**ERC-6492 safety:** A counterfactual wallet address is `CREATE2(factory, salt, initCode)`. +An attacker cannot deploy malicious code to this address without knowing the exact +factory + salt + initCode, which would just deploy the real wallet. + +**Pros:** + +- No node influence; validator address is immutable. +- ERC-4337 wallets that have not rotated keys are supported via ERC-1271. +- ERC-6492 supports counterfactual wallets. +- No governance required. + +**Cons:** + +- **Does not solve ERC-4337 revocability.** If the user's wallet rotates keys before + `createChannel` is submitted, `isValidSignature` fails even for a legitimate state. +- The freezer pattern cannot be added without redeployment, because the freezer address + cannot come from calldata (vulnerability) and there is no extensibility point. +- Session keys cannot be used for `createChannel` (acceptable — see cross-cutting section). + +--- + +### Option C — Upgradeable Bootstrap Validator (single address, governance-controlled) *(UNDESIRABLE)* + +ChannelHub holds a mutable reference to a bootstrap validator contract. Governance +(multisig/DAO) can replace the contract address to add new validation schemes. + +**Pros:** + +- Fully extensible. Any new format can be supported by deploying a new validator and + updating the reference. + +**Cons:** + +- **High-severity governance risk.** Admin can swap the bootstrap validator to a malicious + contract, instantly breaking user signature guarantees for all future `createChannel` + calls. This concentrates enormous power in the admin key. +- Requires timelock + multisig design to be safe, adding significant complexity to the + security-critical path. +- Users cannot verify which bootstrap validator will be used when they sign states. + +--- + +### Option D — User-Controlled Bootstrap Validator Registry *(UNDESIRABLE)* + +Each user maintains their own bootstrap validator registry: +`mapping(address user => mapping(uint8 id => ISignatureValidator))`. + +For `createChannel`, the user sig must use either the default ECDSA validator or a validator +the user has registered under their own address. + +**Pros:** + +- Completely user-controlled. No node or protocol team can influence which validators apply. +- Supports arbitrary wallet types. + +**Cons:** + +- **Requires an extra on-chain transaction.** Users must call `registerBootstrapValidator` + before they can use a custom validator for `createChannel`. This violates constraint 1. +- Bootstrap problem for the registry itself: registration via `msg.sender` works for + ERC-4337 wallets, but ERC-4337 users who want to register before deploying their wallet + have a chicken-and-egg problem. + +--- + +### Option E — `msg.sender == def.user` *(NOT VIABLE)* + +Require the user themselves to submit `createChannel`. + +**Why this is not viable:** + +The protocol's session key model allows users to pre-sign states off-chain so they do not +need to be online per operation. Requiring `msg.sender == def.user` forces the user to be +present and submit the transaction themselves — eliminating the session key benefit. This +directly contradicts constraint 2. + +Additionally, if the user is offline (the precise scenario where a node might need to +enforce a state), the channel cannot be created. + +--- + +### Option F — Protocol-Managed Bootstrap Validator Registry + +ChannelHub maintains a separate, protocol-controlled bootstrap validator registry: + +```solidity +mapping(uint8 bootstrapValidatorId => ISignatureValidator) internal _bootstrapValidators; +address public bootstrapAdmin; // multisig / governance +``` + +`bootstrapAdmin` is the only entity that can add validators to this registry. It is +completely separate from the node validator registry. For `createChannel`, the user sig's +first byte selects a bootstrap validator ID; only IDs present in `_bootstrapValidators` +are accepted. + +Initial registry: + +- id = 0: Default ECDSA / EIP-191 (hardcoded, always available) +- id = 1: ERC-1271 validator (added at deploy time) + +Future additions via governance: + +- id = 2: ERC-4337 freezer bootstrap validator (when ERC-4337 support is needed) +- id = N: future formats + +**ERC-4337 freezer validator (id = 2):** + +A separate `ERC4337BootstrapValidator` contract is deployed with the `SignatureFreezer` +contract address hardcoded. When governance adds it to the bootstrap registry: + +1. User (or relayer) calls `freezer.freeze(channelId, signingData, userSig)` while the + ERC-4337 signature is valid. +2. `createChannel` is called with `userSig = [0x02, originalSig]`. +3. `ERC4337BootstrapValidator` calls `freezer.isFrozen(hash, originalSig, def.user)`. +4. Channel is created. Future key rotations on the ERC-4337 wallet do not affect this. + +The freezer address is **hardcoded in the validator contract**, not in the calldata. +An attacker cannot inject a malicious freezer by manipulating `userSig` bytes. + +**Security comparison with original vulnerability:** + +| | Original attack | Option F attack | +| --- | --- | --- | +| Who adds the malicious validator? | Any node (permissionless) | Only `bootstrapAdmin` (governance) | +| Precondition | None | Admin key compromise | +| Scope | Per-channel, any node can exploit | Affects all channels globally | +| Mitigation | Requires protocol fix | Multisig + timelock on admin | + +Moving from "any node can exploit immediately" to "requires compromising a multisig admin +with a timelock" is a significant security improvement, comparable to the trust model of +most DeFi protocols. + +**Pros:** + +- Extensible without redeployment: governance adds new validators as needed. +- ERC-4337 fully supported via freezer validator (addable by governance, no migration). +- Session keys: unaffected for all operations except `createChannel` (acceptable). +- No node influence on bootstrap registry. +- Future wallet formats can be added incrementally. + +**Cons:** + +- Governance trust assumption: bootstrapAdmin must be secured (multisig + timelock). +- Users cannot verify at signing time which bootstrap validators will be available when + `createChannel` is submitted (mitigated: id=0 ECDSA is always available as fallback). + +--- + +### Option G — Two-Registry System with Tiered Trusted Validators + +Split validator management into two completely independent systems, each with its own +bitmask field in `ChannelDefinition`: + +- **`approvedTrustedValidators`** — bitmask into a protocol-managed registry + (`validatorAdmin`, a hardcoded multisig) +- **`approvedNodeValidators`** — bitmask into the per-node registry (current system) + +Both bitmasks are part of `channelId` and therefore part of every signed state. Adding a +new validator to either registry has zero effect on existing channels — the new ID is not +in their stored bitmasks. + +**Tiered trusted registry — the key structural property:** + +The trusted registry is split into two tiers: + +- **Hardcoded tier** (ids 0–2, immutable in contract bytecode): + - id = 0: Default ECDSA / EIP-191 + - id = 1: ERC-1271 (calls `IERC1271(def.user).isValidSignature`) + - id = 2: ERC-6492 (counterfactual wallet deployment + ERC-1271) +- **Governance tier** (ids 3+, `validatorAdmin`-extensible): + - id = 3: ERC-4337 freezer validator (when needed) + - id = N: future formats + +`createChannel` accepts **only hardcoded-tier IDs** for user sig validation, ignoring +`approvedTrustedValidators` entirely. All subsequent operations accept either tier, +filtered by the channel's stored `approvedTrustedValidators` bitmask. + +**Why this eliminates the admin-compromise-at-createChannel risk:** + +The circular dependency at `createChannel` is broken by construction: no governance action +can influence which validators are usable at channel creation. A compromised `validatorAdmin` +can add malicious validators to the governance tier, but those IDs are rejected by +`createChannel`'s hardcoded-tier check regardless of what appears in calldata. + +For subsequent operations, the bitmask stored at creation time (already signed by the user +via the bootstrap step) gates which governance-tier validators are accepted. A newly added +malicious validator cannot be used on any existing channel. It could only be used on a +future channel where the user explicitly includes that ID in their `approvedTrustedValidators` +— which a well-behaved off-chain client would never do for an unknown ID. + +**ERC-4337 revocability within the hardcoded tier:** + +ERC-4337 wallets are covered by id = 1 (ERC-1271) for the non-rotation case. For the +rotation case, a **FreezerProxy** pattern keeps everything within the hardcoded tier: + +1. User deploys a `FreezerProxy` smart contract as their `def.user` address (or via ERC-6492 + counterfactual deployment, id = 2). +2. `FreezerProxy` implements ERC-1271 and wraps the underlying ERC-4337 wallet. It + permanently records successful validations: `frozenSigs[hash] = true`. +3. Before key rotation, user calls `FreezerProxy.freeze(hash, sig)` — freezer validates + against the current wallet and records the result. +4. `createChannel` calls `IERC1271(def.user).isValidSignature()` → hits FreezerProxy → + returns true regardless of subsequent key rotation. + +No governance action or redeployment is needed for ERC-4337 support. + +**Security comparison across scenarios:** + +| Scenario | Result | +| --- | --- | +| Node registers malicious validator, calls `createChannel` | Rejected — node registry not used for createChannel user sigs | +| `validatorAdmin` compromised, malicious governance-tier validator added | `createChannel` unaffected (hardcoded tier only). Existing channels unaffected (bitmask). New channels unaffected (off-chain client won't include malicious ID). | +| `validatorAdmin` compromised, malicious governance-tier validator added, user explicitly opts in | User's own choice; off-chain client is responsible for warning | + +**Pros:** + +- `createChannel` is fully admin-proof: immutable hardcoded tier eliminates the bootstrap + circular dependency without any governance dependency. +- Existing channels are protected from newly added validators by bitmask isolation. +- Extensible for subsequent operations without redeployment: governance tier grows over time. +- ERC-4337 supported within the hardcoded tier via FreezerProxy + ERC-1271/ERC-6492. +- Clean separation of responsibility: hardcoded tier = bootstrap trust anchor; governance + tier = operational extensibility; node registry = per-node flexibility. +- Session keys unaffected for all operations except `createChannel` (acceptable). + +**Cons:** + +- More structural complexity: two bitmask fields in `ChannelDefinition`, two registry + lookups, tiered logic in `createChannel`. +- FreezerProxy adds deployment overhead for ERC-4337 users who need rotation protection + at the hardcoded tier (avoidable if they accept the governance-tier freezer validator + once added). +- `validatorAdmin` compromise still affects the governance tier for future channels + where users opt in; mitigated by multisig + contract-enforced activation delay. + +**Contract-enforced activation delay:** + +To further limit governance-tier risk, each added validator stores an `activatedAt` +timestamp. `createChannel` (where it applies) and subsequent operations reject validators +where `block.timestamp < activatedAt`. This gives users a guaranteed observation window +even if the multisig is partially compromised. + +```solidity +struct TrustedValidator { + ISignatureValidator validator; + uint64 activatedAt; +} + +mapping(uint8 id => TrustedValidator) internal _trustedValidators; // governance tier +// ids 0-2 hardcoded as immutables, never in this mapping +``` + +### Option H — Per-Node ChannelHub Deployment + +Store a single `node` address immutably in the ChannelHub at deploy time. `createChannel` +requires `def.node == storedNode`; any attempt to open a channel with a different node +address is rejected. + +```solidity +address public immutable NODE; + +constructor(address node, ...) { + NODE = node; +} + +function createChannel(ChannelDefinition calldata def, State calldata initState) external { + require(def.node == NODE, IncorrectNode()); + ... +} +``` + +**Threat model shift, not a direct fix:** + +This option does not eliminate the malicious validator attack — the stored node can still +register a malicious validator, set `approvedSignatureValidators` to include it in +`ChannelDefinition` calldata, and forge a user signature. What it does is change the scope +of who can perform the attack: only the one node bound to this deployment, not any arbitrary +node. + +This matters because users who interact with a given ChannelHub deployment must already +trust the bound node (they sign off-chain states with that node, grant ERC20 allowances to +that contract, etc.). The validator forgery attack therefore sits within the existing trust +boundary — it is one more thing a malicious version of an already-trusted node could do, +rather than something any third-party node can do against users of a shared hub. + +**Validator activation delay (`VALIDATOR_ACTIVATION_DELAY`):** + +Within Option H the bound node still has the ability to register a malicious validator and +immediately weaponise it. A contract-enforced activation delay partially closes this window +for one specific attack surface: + +- Exploiting a newly registered validator to drain **user ERC20 approvals** via a fake + `createChannel(DEPOSIT)` requires the validator to be active first. +- With a 1-day delay, the registration is visible on-chain before it can be used, giving + the node operator (or watchers) time to detect the compromise, broadcast an alert, and + give users time to revoke ERC20 approvals. + +**Pros:** + +- Trivially simple to implement — two lines of code for the node-binding; small struct + change + one timestamp check for the activation delay. +- No changes to the validator selection logic required. +- Cross-node attacks are structurally impossible: a rogue node cannot exploit another node's + ChannelHub to drain users of that node. +- No admin, no governance, no multisig dependency. +- Activation delay creates a detectable, on-chain signal for monitoring before an attack + on ERC20 approvals can execute. +- Compatible with any other option: H can be combined with G to get both per-node scoping + and a proper validator fix. + +**Cons:** + +- **Does not fix the vulnerability within the trust boundary.** The bound node itself retains + the ability to forge user signatures (after the activation delay). Users must accept that + residual risk or combine H with a validator-level fix. +- **One deployment per node.** Nodes cannot share a ChannelHub; each requires its own + deployment and its own separate ERC20 approval from users. +- **Operational fragmentation.** Users interacting with multiple nodes must track multiple + contract addresses and manage approvals per deployment. +- **Activation delay adds operational overhead.** Nodes must pre-register validators 1 day + before first use — a minor one-time cost per validator. + +--- + +## Options Comparison Table + +| | Node-independent | `createChannel` admin-proof | ERC-4337 revocability | Session key (createChannel) | Session key (subsequent ops) | Extensible (no redeploy) | Extra user interaction | Multi-node support | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **A** (immutable set) | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | No | ✓ | +| **B** (universal immutable) | ✓ | ✓ | ✗ (key rotation) | ✗ | ✓ | ✗ | No | ✓ | +| **C** (upgradeable single) | ✓ | ✗ | ✓ (via new validator) | ✗ | ✓ | ✓ | No | ✓ | +| **D** (user registry) | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | **Yes** (registration tx) | ✓ | +| **E** (msg.sender check) | ✓ | ✓ | ✓ | **✗ (breaks model)** | ✓ | ✓ | **Yes** (must be online) | ✓ | +| **F** (protocol registry) | ✓ | ✗ | ✓ (via freezer) | ✗ | ✓ | ✓ | No | ✓ | +| **G** (two-registry + tiered) | ✓ | ✓ | ✓ (via FreezerProxy) | ✗ | ✓ | ✓ | No | ✓ | +| **H** (per-node hub) | Partial (cross-node only) | Partial (cross-node only) | — | ✓ | ✓ | ✓ | No | **✗** | diff --git a/contracts/script/DeployChannelHub.s.sol b/contracts/script/DeployChannelHub.s.sol index 1d12e14f8..36c3ab492 100644 --- a/contracts/script/DeployChannelHub.s.sol +++ b/contracts/script/DeployChannelHub.s.sol @@ -13,11 +13,26 @@ import {ECDSAValidator} from "../src/sigValidators/ECDSAValidator.sol"; * @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. + * broadcast batch via the CREATE2 factory (0x4e59b44847b379578588920ca78fbf26c0b4956c, + * salt = bytes32(0)). Their addresses are deterministic and are logged in the + * summary below. Tx hashes for all deployments are written to the broadcast JSON + * after the script completes. * * Usage: - * DEFAULT_VALIDATOR_ADDR= Address of an already-deployed ISignatureValidator. - * Leave unset to deploy a fresh ECDSAValidator. + * DEFAULT_VALIDATOR_ADDR= Address of an already-deployed ISignatureValidator. + * Leave unset to deploy a fresh ECDSAValidator. + * + * CHANNEL_ENGINE_ADDR= Address of an already-deployed ChannelEngine library. + * ESCROW_DEPOSIT_ENGINE_ADDR= Address of an already-deployed EscrowDepositEngine library. + * ESCROW_WITHDRAWAL_ENGINE_ADDR= Address of an already-deployed EscrowWithdrawalEngine library. + * + * When all three library addresses are provided, Foundry skips their deployment (code + * already exists at those addresses). You must also pass the --libraries flag so the + * linker wires ChannelHub to the existing addresses: + * + * --libraries src/ChannelEngine.sol:ChannelEngine: \ + * --libraries src/EscrowDepositEngine.sol:EscrowDepositEngine: \ + * --libraries src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine: * * forge script script/DeployChannelHub.s.sol:DeployChannelHub \ * --rpc-url \ @@ -30,10 +45,23 @@ 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); + address nodeAddr = vm.envAddress("NODE_ADDR"); + + // Optional: reuse existing library deployments; leave unset to deploy via CREATE2 + address channelEngineAddr = vm.envOr("CHANNEL_ENGINE_ADDR", address(0)); + address escrowDepositAddr = vm.envOr("ESCROW_DEPOSIT_ENGINE_ADDR", address(0)); + address escrowWithdrawalAddr = vm.envOr("ESCROW_WITHDRAWAL_ENGINE_ADDR", address(0)); + + run(defaultValidatorAddr, nodeAddr, channelEngineAddr, escrowDepositAddr, escrowWithdrawalAddr); } - function run(address defaultValidatorAddr) public { + function run( + address defaultValidatorAddr, + address nodeAddr, + address channelEngineAddr, + address escrowDepositAddr, + address escrowWithdrawalAddr + ) public { // msg.sender is set by Foundry to the address derived from --private-key address deployer = msg.sender; @@ -42,13 +70,34 @@ contract DeployChannelHub is Script { 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. + // Resolve library addresses: use provided addresses or fall back to + // the deterministic CREATE2 addresses Foundry would deploy to. // ---------------------------------------------------------------- + bool deployChannelEngine = channelEngineAddr == address(0); + bool deployEscrowDeposit = escrowDepositAddr == address(0); + bool deployEscrowWithdrawal = escrowWithdrawalAddr == address(0); + + if (deployChannelEngine) { + channelEngineAddr = _computeLibraryAddress("ChannelEngine.sol:ChannelEngine"); + } + if (deployEscrowDeposit) { + escrowDepositAddr = _computeLibraryAddress("EscrowDepositEngine.sol:EscrowDepositEngine"); + } + if (deployEscrowWithdrawal) { + escrowWithdrawalAddr = _computeLibraryAddress("EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine"); + } + + // Validate that provided addresses actually have code + if (!deployChannelEngine) { + require(channelEngineAddr.code.length > 0, "DeployChannelHub: CHANNEL_ENGINE_ADDR has no code"); + } + if (!deployEscrowDeposit) { + require(escrowDepositAddr.code.length > 0, "DeployChannelHub: ESCROW_DEPOSIT_ENGINE_ADDR has no code"); + } + if (!deployEscrowWithdrawal) { + require(escrowWithdrawalAddr.code.length > 0, "DeployChannelHub: ESCROW_WITHDRAWAL_ENGINE_ADDR has no code"); + } + uint64 nonce = vm.getNonce(deployer); bool deployValidator = defaultValidatorAddr == address(0); @@ -58,11 +107,16 @@ contract DeployChannelHub is Script { } 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)" + deployChannelEngine ? "ChannelEngine (new): " : "ChannelEngine (existing): ", channelEngineAddr + ); + console.log( + deployEscrowDeposit ? "EscrowDepositEngine (new): " : "EscrowDepositEngine (existing): ", + escrowDepositAddr + ); + console.log( + deployEscrowWithdrawal ? "EscrowWithdrawalEngine (new): " : "EscrowWithdrawalEngine (existing): ", + escrowWithdrawalAddr ); console.log("ChannelHub: ", vm.computeCreateAddress(deployer, nonce)); @@ -76,24 +130,57 @@ contract DeployChannelHub is Script { } // 2. Deploy ChannelHub. - // Foundry detects unlinked library references (ChannelEngine, - // EscrowDepositEngine, EscrowWithdrawalEngine) and inserts their - // deployment transactions before this one in the broadcast batch. + // When library env vars are unset, Foundry detects unlinked library + // references and inserts their deployment transactions before this one + // in the broadcast batch (CREATE2, salt=0). When addresses are provided + // via env vars, Foundry finds code already at those addresses and skips + // their redeployment. require( defaultValidatorAddr.code.length > 0, "DeployChannelHub: DEFAULT_VALIDATOR_ADDR has no code - must be a deployed contract" ); - ChannelHub hub = new ChannelHub(ISignatureValidator(defaultValidatorAddr)); + require(nodeAddr != address(0), "DeployChannelHub: NODE_ADDR must be set"); + ChannelHub hub = new ChannelHub(ISignatureValidator(defaultValidatorAddr), nodeAddr); vm.stopBroadcast(); // ---------------------------------------------------------------- // Summary // ---------------------------------------------------------------- + string memory broadcastFile = + string.concat("broadcast/DeployChannelHub.s.sol/", vm.toString(block.chainid), "/run-latest.json"); + console.log(""); console.log("=== Deployment complete ==="); console.log("DefaultSigValidator:", defaultValidatorAddr); + console.log("Node: ", nodeAddr); console.log("ChannelHub: ", address(hub)); - console.log("(Library addresses are logged in the broadcast JSON)"); + console.log(""); + console.log("=== Libraries ==="); + console.log( + deployChannelEngine ? " ChannelEngine (deployed): " : " ChannelEngine (reused): ", + channelEngineAddr + ); + console.log( + deployEscrowDeposit ? " EscrowDepositEngine (deployed): " : " EscrowDepositEngine (reused): ", + escrowDepositAddr + ); + console.log( + deployEscrowWithdrawal ? " EscrowWithdrawalEngine (deployed): " : " EscrowWithdrawalEngine (reused): ", + escrowWithdrawalAddr + ); + console.log(""); + console.log(string.concat(" (tx hashes: ", broadcastFile, ")")); + } + + // ---------------------------------------------------------------- + // Internal helpers + // ---------------------------------------------------------------- + + /// @dev Compute the deterministic CREATE2 address Foundry uses when + /// auto-deploying an unlinked library: CREATE2_FACTORY, salt=bytes32(0). + function _computeLibraryAddress(string memory artifact) internal view returns (address) { + bytes memory creationCode = vm.getCode(artifact); + return vm.computeCreate2Address(bytes32(0), keccak256(creationCode), CREATE2_FACTORY); } } diff --git a/contracts/script/DepositToNode.s.sol b/contracts/script/DepositToNode.s.sol new file mode 100644 index 000000000..d3e9ad48c --- /dev/null +++ b/contracts/script/DepositToNode.s.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +interface IChannelHub { + /// @notice Deposits ERC-20 tokens into the node balance on ChannelHub. + /// @param token ERC-20 token address. + /// @param amount Raw token amount in token decimals. + function depositToNode(address token, uint256 amount) external payable; +} + +/** + * @title DepositToNode + * @notice Approve a token spend and deposit to the node on the current chain. + * @dev Single-chain, single-token script. Multi-chain / multi-token orchestration + * is handled by batchDepositToNode.sh, which calls this script once per token. + * + * Direct usage: + * forge script script/DepositToNode.s.sol \ + * --sig "run(address,address,uint256)" \ + * --rpc-url \ + * --broadcast \ + * [--account | -i | --ledger] + */ +contract DepositToNode is Script { + using SafeERC20 for IERC20; + + /** + * @notice Approve hub to spend token then call depositToNode. + * @param hub ChannelHub contract address on the current chain. + * @param token ERC-20 token address to deposit. + * @param amount Token amount in the token's native decimals. + */ + function run(address hub, address token, uint256 amount) external { + require(hub != address(0), "hub=0"); + require(amount > 0, "amount=0"); + + console.log("=== DepositToNode ==="); + console.log("Chain: ", block.chainid); + console.log("Hub: ", hub); + console.log("Token: ", token); + console.log("Amount: ", amount); + + if (token == address(0)) { + vm.broadcast(); + // For ETH deposits, the amount is sent as msg.value and no approval is needed + IChannelHub(hub).depositToNode{value: amount}(token, amount); + return; + } + + vm.startBroadcast(); + // forceApprove handles non-standard tokens (e.g. USDT) that don't return bool + IERC20(token).forceApprove(hub, amount); + IChannelHub(hub).depositToNode(token, amount); + vm.stopBroadcast(); + } +} diff --git a/contracts/script/RegisterNodeValidator.s.sol b/contracts/script/RegisterNodeValidator.s.sol index 9e6de1cbc..2092fa6ed 100644 --- a/contracts/script/RegisterNodeValidator.s.sol +++ b/contracts/script/RegisterNodeValidator.s.sol @@ -9,6 +9,7 @@ import {TestUtils} from "../test/TestUtils.sol"; import {ChannelHub} from "../src/ChannelHub.sol"; import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; +import {Utils} from "../src/Utils.sol"; /** * @title RegisterNodeValidator @@ -59,14 +60,14 @@ contract RegisterNodeValidator is Script { console.log("Validator address:", validatorAddress); console.log("Chain ID:", block.chainid); - bytes memory message = abi.encode(validatorId, validatorAddress, block.chainid); + bytes memory message = Utils.getValidatorRegistrationMessage(channelHubAddress, validatorId, validatorAddress); console.log("Message hash:", vm.toString(keccak256(message))); bytes memory signature = TestUtils.signEip191(vm, nodePrivateKey, message); console.log("Signature:", vm.toString(signature)); vm.broadcast(); - channelHub.registerNodeValidator(nodeAddress, validatorId, ISignatureValidator(validatorAddress), signature); + channelHub.registerNodeValidator(validatorId, ISignatureValidator(validatorAddress), signature); console.log("Validator registered successfully!"); } diff --git a/contracts/script/batchDepositToNode.md b/contracts/script/batchDepositToNode.md new file mode 100644 index 000000000..5dd322ceb --- /dev/null +++ b/contracts/script/batchDepositToNode.md @@ -0,0 +1,147 @@ +# batchDepositToNode + +Batch `approve` + `depositToNode` across multiple chains and tokens. + +Chains run in parallel; tokens within each chain run sequentially with on-chain +confirmation waited between each token (nonce safety). Token amounts in the config +are human-readable (e.g. `1000` for 1000 USDC) — the script fetches `decimals()` +from each token contract and converts automatically. + +## Prerequisites + +- [Foundry](https://getfoundry.sh/) (`forge`, `cast`) +- `jq` +- A signing method: keystore account, interactive key entry, or Ledger + +**Set up a keystore account (recommended):** +```bash +cast wallet import topup-signer --interactive +# Prompts for private key and encryption password. +# Key is stored encrypted in ~/.foundry/keystores/topup-signer +``` + +## Config format + +Create a `batchDepositToNode.json` file (not committed — contains RPC URLs): + +```json +[ + { + "rpcUrl": "https://mainnet.infura.io/v3/YOUR_KEY", + "hubAddress": "0xCe87FD88F4B5Fd5475d163e2642C5c2c7dD655Ec", + "tokens": [ + { "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "1000" }, + { "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "amount": "500" } + ] + }, + { + "rpcUrl": "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY", + "hubAddress": "0x...", + "tokens": [ + { "address": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", "amount": "1000" } + ] + } +] +``` + +`amount` is in human-readable token units (e.g. `"1000"` = 1000 USDC). The script +fetches `decimals()` from the token contract at runtime and computes the raw on-chain +value (`1000 × 10^6 = 1000000000` for USDC). Fractional amounts are supported +(e.g. `"0.5"`), but values requiring more than 15 significant digits may lose +precision due to floating-point arithmetic — use whole numbers for large amounts. + +## Usage + +Run from the `contracts/` directory: + +```bash +# Keystore account — password prompted once, reused across all chains and tokens +./script/batchDepositToNode.sh batchDepositToNode.json --account topup-signer + +# Interactive private key — prompted once, temp keystore created automatically +./script/batchDepositToNode.sh batchDepositToNode.json -i + +# Ledger hardware wallet (sequential only — see note below) +./script/batchDepositToNode.sh batchDepositToNode.json -- --ledger + +# Dry run — simulate without broadcasting (no txs sent, no confirmation wait) +./script/batchDepositToNode.sh batchDepositToNode.json --dry-run --account topup-signer + +# Extra forge flags after -- (e.g. verbosity) +./script/batchDepositToNode.sh batchDepositToNode.json --account topup-signer -- -vvvv +``` + +`--account` and `-i` are shell-level flags handled before any forge invocations — +the password or key is collected once, then all forge calls reuse it silently. +Anything after `--` is forwarded verbatim to every `forge script` call. + +> **`-i` security note:** The private key is prompted interactively (echo off, not +> in shell history). Internally the script runs one `cast wallet import` call to +> create a temp keystore — the key appears in the process list for that one brief +> call only, then the keystore (random password, deleted on exit) is used for all +> subsequent forge invocations. Clear your clipboard after pasting: `pbcopy < /dev/null`. + +> **Ledger note:** `--ledger` requires sequential chain execution. The Ledger USB +> device can only be opened by one process at a time — running chains in parallel +> will cause all but the first to fail immediately. For Ledger, run chains one at a +> time by passing a single-entry config, or iterate manually. + +## Output + +A timestamped log file is written to the directory where the script is invoked: + +```text +batchDepositToNode-20260520-143000.log +``` + +All `forge` and `cast` output is captured. On completion: + +```text +=== Summary === +Deposited 4 tokens across 3 chains +``` + +On partial failure: + +```text +=== Summary === +Deposited 3 tokens across 3 chains + +FAILED: + chain=42161: 0xFF970A61... +``` + +Failed tokens are reported but do not stop other chains or tokens from running. +Exit code is `0` on full success, `1` if any token failed. + +## How it works + +```text +batchDepositToNode.sh +├── chain 1 (background) ──► token A: fetch decimals → approve + depositToNode → await confirm +│ └──► token B: fetch decimals → approve + depositToNode → await confirm +├── chain 2 (background) ──► token A: fetch decimals → approve + depositToNode → await confirm +│ └──► ... +└── wait for all → summary +``` + +For each token, the script: +1. Calls `cast call "decimals()(uint8)"` to get the token's decimal precision +2. Multiplies the human-readable `amount` by `10^decimals` to get the raw on-chain value +3. Runs `forge script DepositToNode.s.sol` with the raw amount (`approve` + `depositToNode`) +4. Reads `broadcast/DepositToNode.s.sol//run-latest.json` and calls + `cast receipt --confirmations 1` on both tx hashes before moving to the next token + +## Single-chain manual run + +`DepositToNode.s.sol` can also be called directly. Note that `` must be the +raw on-chain value (already multiplied by decimals): + +```bash +# Example: 1000 USDC = 1000000000 (6 decimals) +forge script script/DepositToNode.s.sol \ + --sig "run(address,address,uint256)" \ + --rpc-url \ + --broadcast \ + --account topup-signer +``` diff --git a/contracts/script/batchDepositToNode.sh b/contracts/script/batchDepositToNode.sh new file mode 100755 index 000000000..1659884fe --- /dev/null +++ b/contracts/script/batchDepositToNode.sh @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +# batchDepositToNode.sh — approve + depositToNode across multiple chains in parallel. +# See batchDepositToNode.md for config format and usage examples. +set -uo pipefail + +# ── Usage ───────────────────────────────────────────────────────────────────── + +usage() { + echo "Usage: $0 [--dry-run] [--account | -i] [-- ...]" + echo "" + echo " config.json Path to batch config (see batchDepositToNode.md)" + echo " --dry-run Simulate without broadcasting; skips confirmation wait" + echo " --account Keystore account — prompts for password once, reused across all invocations" + echo " -i Interactive private key — prompts once, creates temp keystore" + echo " -- Forward remaining args to every forge script invocation" + echo " e.g.: -- --ledger" + echo " e.g.: -- -vvvv" + exit 1 +} + +# ── Argument parsing ────────────────────────────────────────────────────────── + +CONFIG="" +DRY_RUN=false +ACCOUNT_NAME="" +INTERACTIVE_KEY=false +FORGE_EXTRA=() + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage ;; + --dry-run) DRY_RUN=true; shift ;; + --account) + [[ -z "${2:-}" ]] && { echo "Error: --account requires a name" >&2; usage; } + ACCOUNT_NAME="$2"; shift 2 ;; + -i) INTERACTIVE_KEY=true; shift ;; + --) + shift + FORGE_EXTRA=("$@") + break + ;; + -*) + echo "Error: unknown flag: $1" >&2 + usage + ;; + *) + if [[ -z "$CONFIG" ]]; then + CONFIG="$1" + else + echo "Error: unexpected argument: $1" >&2 + usage + fi + shift + ;; + esac +done + +[[ -z "$CONFIG" ]] && { echo "Error: config file required" >&2; usage; } +[[ -f "$CONFIG" ]] || { echo "Error: config not found: $CONFIG" >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "Error: jq is required" >&2; exit 1; } +command -v cast >/dev/null 2>&1 || { echo "Error: cast (foundry) is required" >&2; exit 1; } +[[ -f foundry.toml ]] || { echo "Error: must be run from the contracts/ directory" >&2; exit 1; } + +if [[ -n "$ACCOUNT_NAME" && "$INTERACTIVE_KEY" == true ]]; then + echo "Error: --account and -i are mutually exclusive" >&2; exit 1 +fi + +# ── Signing setup (runs before logging so prompts appear on tty) ────────────── + +TMPKS="" +TMPPW="" +SIGNING_ARGS=() + +if [[ -n "$ACCOUNT_NAME" ]]; then + TMPPW=$(mktemp) + read -rsp "Keystore password for '$ACCOUNT_NAME': " KS_PASS; echo + printf '%s' "$KS_PASS" > "$TMPPW" + unset KS_PASS + SIGNING_ARGS=(--account "$ACCOUNT_NAME" --password-file "$TMPPW") + +elif $INTERACTIVE_KEY; then + TMPKS=$(mktemp -d) + TMPPW=$(mktemp) + # Random password for the temp keystore — user never needs to see it + LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 32 > "$TMPPW" \ + || openssl rand -base64 24 > "$TMPPW" + read -rsp "Private key (hex, with or without 0x): " PK; echo + # Import to temp keystore — key and password briefly in process args for this one call only + if ! cast wallet import "batch-$$" \ + --keystore-dir "$TMPKS" \ + --private-key "$PK" \ + --unsafe-password "$(cat "$TMPPW")" \ + >/dev/null 2>&1; then + echo "Error: failed to import key into temporary keystore (TMPKS=$TMPKS)" >&2 + exit 1 + fi + unset PK + SIGNING_ARGS=(--keystore "$TMPKS/batch-$$" --password-file "$TMPPW") +fi + +# ── Setup ───────────────────────────────────────────────────────────────────── + +LOGFILE="$(pwd)/batchDepositToNode-$(date +%Y%m%d-%H%M%S).log" +RESULTS=$(mktemp -d) +trap 'rm -rf "$RESULTS" "${TMPKS:-}" "${TMPPW:-}"' EXIT + +exec > >(tee -a "$LOGFILE") 2>&1 + +echo "Log: $LOGFILE" +echo "Config: $CONFIG" +echo "Dry-run: $DRY_RUN" +echo "Started: $(date -u +%Y-%m-%dT%H:%M:%SZ)" +echo + +if $DRY_RUN; then + BROADCAST_ARGS=() + echo "*** DRY RUN — no transactions will be broadcast ***" + echo +else + BROADCAST_ARGS=(--broadcast) +fi + +# ── Per-chain worker ────────────────────────────────────────────────────────── +# Each chain runs as an independent background subshell. +# Tokens within a chain run sequentially; on-chain confirmation is awaited +# after each token before starting the next. + +pids=() +chain_idx=0 + +while read -r chain; do + rpc=$(echo "$chain" | jq -r '.rpcUrl') + hub=$(echo "$chain" | jq -r '.hubAddress') + idx=$chain_idx + chain_idx=$((chain_idx + 1)) + + ( + ok=0 + fail=0 + fail_tokens="" + chain_id=$(cast chain-id --rpc-url "$rpc" 2>/dev/null) + if [[ -z "$chain_id" ]]; then + echo "!!! FAILED: cannot reach RPC $rpc — skipping chain" + jq -n --arg rpc "$rpc" --arg chain_id "unknown" \ + --argjson ok 0 --argjson fail 1 \ + --arg fail_tokens "ALL (RPC unreachable)" \ + '{rpc:$rpc, chain_id:$chain_id, ok:$ok, fail:$fail, fail_tokens:$fail_tokens}' \ + > "$RESULTS/$idx.json" + exit 1 + fi + + while read -r entry; do + token=$(echo "$entry" | jq -r '.address') + human_amount=$(echo "$entry" | jq -r '.amount') + + # Fetch token decimals from chain and compute raw on-chain amount. + # Native token (zero address): skip decimals() call and hard-code 18. The vast + # majority of EVM-compatible networks use 18 decimals for their native asset. + # If a chain uses fewer, the tx will most likely fail due to insufficient funds + # before any harm is done. + if [[ "$token" == "0x0000000000000000000000000000000000000000" ]]; then + decimals=18 + else + decimals=$(cast call "$token" "decimals()(uint8)" --rpc-url "$rpc" 2>/dev/null) + if [[ -z "$decimals" ]]; then + echo "!!! FAILED: [chain=$chain_id] could not fetch decimals() for $token" + fail=$((fail + 1)) + fail_tokens="$fail_tokens $token" + continue + fi + fi + raw_amount=$(python3 - "$human_amount" "$decimals" <<'PY' +from decimal import Decimal, InvalidOperation +import sys +human, decimals = sys.argv[1], int(sys.argv[2]) +try: + scaled = Decimal(human) * (Decimal(10) ** decimals) +except InvalidOperation: + print("INVALID_AMOUNT", end=""); sys.exit(2) +if scaled != scaled.to_integral_value(): + print("NON_INTEGER_RAW_AMOUNT", end=""); sys.exit(3) +print(int(scaled), end="") +PY + ) || { + echo "!!! FAILED: [chain=$chain_id] invalid amount '$human_amount' for token=$token" + fail=$((fail + 1)) + fail_tokens="$fail_tokens $token" + continue + } + + echo ">>> [chain=$chain_id] token=$token amount=$human_amount (decimals=$decimals raw=$raw_amount)" + + if forge script script/DepositToNode.s.sol \ + --sig "run(address,address,uint256)" "$hub" "$token" "$raw_amount" \ + --rpc-url "$rpc" \ + ${BROADCAST_ARGS[@]+"${BROADCAST_ARGS[@]}"} \ + ${SIGNING_ARGS[@]+"${SIGNING_ARGS[@]}"} \ + ${FORGE_EXTRA[@]+"${FORGE_EXTRA[@]}"}; then + + confirm_failed=false + if ! $DRY_RUN; then + broadcast="broadcast/DepositToNode.s.sol/$chain_id/run-latest.json" + if [[ -f "$broadcast" ]]; then + while read -r hash; do + echo " awaiting $hash ..." + if ! cast receipt --confirmations 1 "$hash" --rpc-url "$rpc" >/dev/null; then + echo "!!! FAILED: could not confirm $hash" + confirm_failed=true + fi + done < <(jq -r '.transactions[].hash' "$broadcast") + fi + fi + + if $confirm_failed; then + fail=$((fail + 1)) + fail_tokens="$fail_tokens $token" + echo "!!! FAILED: [chain=$chain_id] token=$token unconfirmed tx" + else + ok=$((ok + 1)) + echo " confirmed: [chain=$chain_id] token=$token amount=$human_amount" + fi + else + fail=$((fail + 1)) + fail_tokens="$fail_tokens $token" + echo "!!! FAILED: [chain=$chain_id] token=$token" + fi + done < <(echo "$chain" | jq -c '.tokens[]') + + jq -n \ + --arg rpc "$rpc" \ + --arg chain_id "$chain_id" \ + --argjson ok "$ok" \ + --argjson fail "$fail" \ + --arg fail_tokens "$fail_tokens" \ + '{rpc:$rpc, chain_id:$chain_id, ok:$ok, fail:$fail, fail_tokens:$fail_tokens}' \ + > "$RESULTS/$idx.json" + ) & + + pids+=($!) +done < <(jq -c '.[]' "$CONFIG") + +# ── Collect results ─────────────────────────────────────────────────────────── + +for pid in "${pids[@]}"; do + wait "$pid" || true # failures reported via result files, not exit codes +done + +# ── Summary ─────────────────────────────────────────────────────────────────── + +echo +echo "=== Summary ===" + +total_ok=0 +total_chains=0 +fail_reports=() + +for f in "$RESULTS"/*.json; do + [[ -f "$f" ]] || continue + total_chains=$((total_chains + 1)) + ok=$(jq -r '.ok' "$f") + fail=$(jq -r '.fail' "$f") + chain_id=$(jq -r '.chain_id' "$f") + fail_tokens=$(jq -r '.fail_tokens' "$f") + total_ok=$((total_ok + ok)) + [[ "$fail" -gt 0 ]] && fail_reports+=("chain=$chain_id: $fail_tokens") +done + +echo "Deposited $total_ok tokens across $total_chains chains" + +if [[ "${#fail_reports[@]}" -gt 0 ]]; then + echo + echo "FAILED:" + printf ' %s\n' "${fail_reports[@]}" + exit 1 +fi diff --git a/contracts/signature-validators.md b/contracts/signature-validators.md index 408da7874..b98896e25 100644 --- a/contracts/signature-validators.md +++ b/contracts/signature-validators.md @@ -8,11 +8,12 @@ This document describes the pluggable signature validation system in the Nitroli The protocol supports flexible signature validation through the `ISignatureValidator` interface. All validators implement: -- `validateSignature(channelId, signingData, signature, participant)` - Validates a participant's signature +- `validateSignature(channelId, signingData, signature, participant)` — Validates a state signature +- `validateChallengeSignature(channelId, signingData, signature, participant)` — Validates a challenge signature Validators receive the core state data (`signingData`) and `channelId` separately, allowing them to construct the full message according to their signing scheme. -For challenge signatures, ChannelHub appends `"challenge"` to the signing data before calling `validateSignature`. +For challenge signatures, ChannelHub calls `validateChallengeSignature` rather than `validateSignature`. Each validator is responsible for constructing the challenge message and enforcing any validator-specific constraints (e.g., temporal bounds for session keys). --- @@ -20,12 +21,12 @@ For challenge signatures, ChannelHub appends `"challenge"` to the signing data b ### Node Validator Registry -The protocol uses a **per-node validator registry** system. Each node can register signature validators and assign them 1-byte identifiers (0x01-0xFF). +The protocol uses a **validator registry** system. NODE can register signature validators and assign them 1-byte identifiers (0x01-0xFF). **Design rationale:** In the Nitrolite off-chain protocol, the node acts as the orchestrator and decides which signature validators are supported for **node signatures**. This ensures: -- Nodes can enforce their security requirements for their own signatures -- Nodes benefit from flexible validator implementations (SessionKey, multi-sig, etc.) +- NODE can enforce its security requirements for its own signatures +- NODE benefits from flexible validator implementations (SessionKey, multi-sig, etc.) - Cross-chain compatibility (validator addresses don't affect channelId or signature verification) ### Security Consideration: Preventing Signature Forgery @@ -58,7 +59,7 @@ This approach provides security (users control the agreed validators), cross-cha ### Validator Registration -Nodes register validators by providing a signature over the validator configuration. This allows node operators to use cold storage or hardware wallets without exposing private keys to send transactions. +NODE registers validators by providing a signature over the validator configuration. This allows node operators to use cold storage or hardware wallets without exposing private keys to send transactions. **Registration message:** @@ -80,7 +81,7 @@ The signature is verified using ECDSA recovery: - Node's private key only signs, never sends transactions - Validator ID 0x00 is reserved for the default validator - Registration is immutable (cannot change once set) -- 255 validators per node (0x01-0xFF) +- 255 validators (0x01-0xFF) ### Signature Format @@ -154,6 +155,10 @@ Default validator supporting standard ECDSA signatures. Automatically tries both 2. If fails, try raw ECDSA recovery 3. Return `VALIDATION_SUCCESS` if recovered address matches participant, `VALIDATION_FAILURE` otherwise +### Challenge Validation + +`validateChallengeSignature` delegates to `validateSignature` with the signing data extended by a `"challenge"` suffix. The signer must sign `pack(channelId, signingData || "challenge")`. No temporal or scope checks apply — ECDSA keys do not expire. + ### Use Cases - Standard wallet signatures (MetaMask, WalletConnect, hardware wallets) @@ -186,14 +191,21 @@ bytes sigBody = abi.encode(SessionKeyAuthorization, bytes sessionKeySignature) **Two checks:** -1. Participant authorized the session key: `authData = abi.encode(sessionKey, metadataHash)` +1. Participant authorized the session key: `authData = abi.encode(SESSION_KEY_AUTH_TYPEHASH, sessionKey, metadataHash)` + where `SESSION_KEY_AUTH_TYPEHASH = keccak256("Nitrolite.SessionKey(address sessionKey,bytes32 metadataHash)")` 2. Session key signed the state Both use EIP-191 first, then raw ECDSA if that fails. ### Metadata -Application-defined data encoding expiration, allowed channels, and permissions. **Validated off-chain by Clearnode, not on-chain.** +Application-defined data encoding expiration, allowed channels, and permissions. **Validated off-chain by Nitronode, not on-chain.** + +### Challenge Signatures + +`validateChallengeSignature` is **not supported** and always reverts with `ChallengeWithSessionKeyNotSupported`. + +This is to prevent a vulnerability: since session key authorizations are permanently valid on-chain (expiration is opaque in metadataHash), allowing session keys to challenge would let any expired or revoked key put channels into `DISPUTED` state unilaterally, bypassing Nitronode's off-chain enforcement and causing a DoS on the channel. --- @@ -207,9 +219,9 @@ Application-defined data encoding expiration, allowed channels, and permissions. It is safe for users because: -- Clearnode validates metadata (expiration, scope, permissions) +- Nitronode validates metadata (expiration, scope, permissions) - Node must countersign (provides protection) -- Limited damage if compromised (Clearnode rejects invalid requests) +- Limited damage if compromised (Nitronode rejects invalid requests) - Revocable (switch to main key anytime) ### Nodes: Unsafe ⚠️ diff --git a/contracts/src/ChannelEngine.sol b/contracts/src/ChannelEngine.sol index 7abef8b70..e20325ac3 100644 --- a/contracts/src/ChannelEngine.sol +++ b/contracts/src/ChannelEngine.sol @@ -27,6 +27,7 @@ library ChannelEngine { error IncorrectChannelStatus(); error IncorrectStateVersion(); error ChallengeExpired(); + error TokenMismatch(); error IncorrectUserAllocation(); error IncorrectNodeAllocation(); @@ -64,7 +65,6 @@ library ChannelEngine { // State updates ChannelStatus newStatus; uint64 newChallengeExpiry; - bool updateLastState; bool closeChannel; } @@ -101,6 +101,11 @@ library ChannelEngine { require(candidate.homeLedger.chainId == block.chainid, IncorrectHomeChainId()); require(candidate.version > ctx.prevState.version || Utils.isEmpty(ctx.prevState), IncorrectStateVersion()); + // Token must remain constant throughout the channel's lifetime + if (!Utils.isEmpty(ctx.prevState)) { + require(candidate.homeLedger.token == ctx.prevState.homeLedger.token, TokenMismatch()); + } + // Validate token decimals for homeLedger Utils.validateTokenDecimals(candidate.homeLedger); @@ -167,7 +172,6 @@ library ChannelEngine { revert IncorrectStateIntent(); } - effects.updateLastState = true; return effects; } @@ -251,13 +255,11 @@ library ChannelEngine { IncorrectChannelStatus() ); - uint256 allocsSum = candidate.homeLedger.userAllocation + candidate.homeLedger.nodeAllocation; - require(allocsSum <= ctx.lockedFunds, AllocationExceedsLockedFunds()); + require(candidate.homeLedger.userAllocation == 0, IncorrectUserAllocation()); + require(candidate.homeLedger.nodeAllocation == 0, IncorrectNodeAllocation()); - // Ensure final locked funds will be sufficient for special nodeAllocation handling int256 finalLockedFunds = ctx.lockedFunds.toInt256() + userNfDelta + nodeNfDelta; require(finalLockedFunds >= 0, InsufficientLockedFunds()); - require(finalLockedFunds >= candidate.homeLedger.nodeAllocation.toInt256(), InsufficientLockedFunds()); // Calculate effects // Push allocations to parties (negative = push out from channel) @@ -495,14 +497,15 @@ library ChannelEngine { if (ctx.status == ChannelStatus.MIGRATING_IN) { // NEW HOME CHAIN (IN): Move MIGRATING_IN → OPERATING - // The home state represents the new home (current chain) + // The homeLedger represents the new home (current chain) in candidate and prevState require(candidate.homeLedger.chainId == block.chainid, IncorrectHomeChainId()); require(ctx.prevState.intent == StateIntent.INITIATE_MIGRATION, IncorrectPreviousStateIntent()); require(candidate.version == ctx.prevState.version + 1, IncorrectStateVersion()); - uint256 userMigratedAlloc = ctx.prevState.homeLedger.userAllocation; + // nonHomeLedger = old home (holds the user's migrated allocation) + uint256 userMigratedAlloc = ctx.prevState.nonHomeLedger.userAllocation; - // Validate that this completes the migration + // Validate that this completes the migration: user receives their migrated allocation on new home require(candidate.homeLedger.userAllocation == userMigratedAlloc, IncorrectUserAllocation()); require(candidate.homeLedger.nodeAllocation == 0, IncorrectNodeAllocation()); require(candidate.nonHomeLedger.userAllocation == 0, IncorrectUserAllocation()); @@ -519,6 +522,12 @@ library ChannelEngine { } else if (ctx.status == ChannelStatus.OPERATING || ctx.status == ChannelStatus.DISPUTED) { // OLD HOME CHAIN (OUT): Release funds and move to MIGRATED_OUT // homeLedger represents old home (current chain) + require(candidate.homeLedger.chainId == block.chainid, IncorrectHomeChainId()); + if (ctx.prevState.intent == StateIntent.INITIATE_MIGRATION) { + require(candidate.version == ctx.prevState.version + 1, IncorrectStateVersion()); + } else { + require(candidate.version > ctx.prevState.version + 1, IncorrectStateVersion()); + } // Validate homeLedger require(candidate.homeLedger.userAllocation == 0, IncorrectUserAllocation()); diff --git a/contracts/src/ChannelHub.sol b/contracts/src/ChannelHub.sol index bdade7b6d..ea448cadb 100644 --- a/contracts/src/ChannelHub.sol +++ b/contracts/src/ChannelHub.sol @@ -9,7 +9,6 @@ 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 {IVault} from "./interfaces/IVault.sol"; import {ISignatureValidator, ValidationResult, VALIDATION_FAILURE} from "./interfaces/ISignatureValidator.sol"; import { ChannelDefinition, @@ -31,8 +30,28 @@ import {EcdsaSignatureUtils} from "./sigValidators/EcdsaSignatureUtils.sol"; * @title ChannelHub * @notice Main contract implementing the Nitrolite state channel protocol (single-chain operations) * @dev Uses unified transition pattern with ChannelEngine library for validation + * + * TOKEN COMPATIBILITY: + * + * Only standard ERC20 tokens and native ETH are supported. The following token types are + * incompatible with the static ledger model and must not be used: + * + * - Rebasing tokens (e.g. stETH, aTokens): their autonomous balance changes are invisible to the + * ledger and create unrecoverable accounting divergence. Use non-rebasing wrappers (e.g. wstETH). + * - Fee-on-transfer tokens: the amount received by the contract is less than the amount recorded, + * causing the ledger to overstate holdings from the very first deposit. + * + * There is no hard-coded guardrail preventing deposit of these tokens — the contract will accept + * them, but any discrepancy will produce undefined accounting behavior for all users of that token. + * Enforcement is off-chain: the Node will not sign states that reference unsupported token types. + * + * NATIVE ETH vs ERC20 DEPOSIT ASYMMETRY: + * + * When user funds are pulled, ERC20 uses `transferFrom` (caller-agnostic, requires prior approval), + * while native ETH requires `msg.value == amount` (the transaction submitter must supply the ETH). + * For native ETH channels, deposit states must therefore be submitted by the user or a willing proxy. */ -contract ChannelHub is IVault, ReentrancyGuard { +contract ChannelHub is ReentrancyGuard { using EnumerableSet for EnumerableSet.Bytes32Set; using SafeERC20 for IERC20; using SafeCast for int256; @@ -40,14 +59,13 @@ contract ChannelHub is IVault, ReentrancyGuard { using ECDSA for bytes32; using MessageHashUtils for bytes; - event EscrowDepositsPurged(uint256 purgedCount); + event Deposited(address indexed token, uint256 amount); + event Withdrawn(address indexed token, uint256 amount); + + event EscrowDepositsPurged(bytes32[] escrowIds, uint256 purgedCount); event ChannelCreated( - bytes32 indexed channelId, - address indexed user, - address indexed node, - ChannelDefinition definition, - State initialState + bytes32 indexed channelId, address indexed user, ChannelDefinition definition, State initialState ); event ChannelDeposited(bytes32 indexed channelId, State candidate); event ChannelWithdrawn(bytes32 indexed channelId, State candidate); @@ -58,24 +76,54 @@ contract ChannelHub is IVault, ReentrancyGuard { event EscrowDepositInitiated(bytes32 indexed escrowId, bytes32 indexed channelId, State state); event EscrowDepositInitiatedOnHome(bytes32 indexed escrowId, bytes32 indexed channelId, State state); event EscrowDepositChallenged(bytes32 indexed escrowId, State state, uint64 challengeExpireAt); + + /// @dev Informational event: emitted only when finalizeEscrowDeposit() is called on the non-home chain via its + /// dedicated function path. It is not emitted if a newer state is enforced on the channel that supersedes the + /// finalization. External consumers must not treat this as an exhaustive signal. event EscrowDepositFinalized(bytes32 indexed escrowId, bytes32 indexed channelId, State state); + + /// @dev Informational event: emitted only when the home-chain channel advances through the dedicated + /// finalizeEscrowDeposit() path. It is not emitted if the channel state is advanced by a newer signed state that + /// supersedes the escrow finalization. External consumers must not treat this as an exhaustive signal. event EscrowDepositFinalizedOnHome(bytes32 indexed escrowId, bytes32 indexed channelId, State state); event EscrowWithdrawalInitiated(bytes32 indexed escrowId, bytes32 indexed channelId, State state); + + /// @dev Informational event: emitted only when initiateEscrowWithdrawal() advances the home-chain channel via its + /// dedicated function path. It is not emitted if a newer signed state supersedes the initiation on that channel. + /// External consumers must not treat this as an exhaustive signal. event EscrowWithdrawalInitiatedOnHome(bytes32 indexed escrowId, bytes32 indexed channelId, State state); + event EscrowWithdrawalChallenged(bytes32 indexed escrowId, State state, uint64 challengeExpireAt); event EscrowWithdrawalFinalized(bytes32 indexed escrowId, bytes32 indexed channelId, State state); + + /// @dev Informational event: emitted only when finalizeEscrowWithdrawal() advances the home-chain channel via its + /// dedicated function path. It is not emitted if a newer signed state supersedes the finalization on that channel. + /// External consumers must not treat this as an exhaustive signal. event EscrowWithdrawalFinalizedOnHome(bytes32 indexed escrowId, bytes32 indexed channelId, State state); + /// @dev Informational event: emitted only when initiateMigration() is called on the old home chain via its + /// dedicated function path. It is not emitted if a newer signed state (e.g. FINALIZE_MIGRATION) is enforced + /// directly on the channel, bypassing the explicit initiation path. External consumers must not treat this as + /// an exhaustive signal. event MigrationOutInitiated(bytes32 indexed channelId, State state); + event MigrationInInitiated(bytes32 indexed channelId, State state); event MigrationOutFinalized(bytes32 indexed channelId, State state); + + /// @dev Informational event: emitted only when finalizeMigration() is called explicitly on the new home chain. + /// A MIGRATING_IN channel may transition to OPERATING through any standard channel operation (deposit, withdraw, + /// checkpoint, challenge) built on top of a FINALIZE_MIGRATION state, in which case this event is not emitted. + /// External consumers must not treat this as an exhaustive signal; the canonical migration completion signal is + /// MigrationOutFinalized, which is always emitted unconditionally on the old home chain. event MigrationInFinalized(bytes32 indexed channelId, State state); - event ValidatorRegistered(address indexed node, uint8 indexed validatorId, ISignatureValidator indexed validator); + event ValidatorRegistered(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 NodeBalanceUpdated(address indexed token, uint256 amount); + error InvalidAddress(); error IncorrectAmount(); error IncorrectValue(); @@ -84,9 +132,10 @@ contract ChannelHub is IVault, ReentrancyGuard { error IncorrectChallengeDuration(); error InvalidValidatorId(); - error ValidatorAlreadyRegistered(address node, uint8 validatorId); - error ValidatorNotRegistered(address node, uint8 validatorId); + error ValidatorAlreadyRegistered(uint8 validatorId); + error ValidatorNotRegistered(uint8 validatorId); error ValidatorNotApproved(); + error ValidatorNotActive(uint8 validatorId, uint64 activatesAt); error EmptySignature(); error IncorrectSignature(); @@ -94,8 +143,10 @@ contract ChannelHub is IVault, ReentrancyGuard { error IncorrectStateIntent(); error IncorrectChannelStatus(); error ChallengerVersionTooLow(); - error NoChannelIdFound(); + error NoChannelIdFoundForEscrow(); error IncorrectChannelId(); + error IncorrectNode(); + error IncorrectMsgSender(); struct ChannelMeta { ChannelStatus status; @@ -109,7 +160,6 @@ contract ChannelHub is IVault, ReentrancyGuard { bytes32 channelId; EscrowStatus status; address user; - address node; uint256 approvedSignatureValidators; uint64 unlockAt; uint64 challengeExpireAt; @@ -121,33 +171,46 @@ contract ChannelHub is IVault, ReentrancyGuard { bytes32 channelId; EscrowStatus status; address user; - address node; uint256 approvedSignatureValidators; uint64 challengeExpireAt; uint256 lockedAmount; State initState; } + struct NodeValidator { + ISignatureValidator validator; + uint64 registeredAt; + } + // ======== Contract Storage ========== uint8 public constant VERSION = 1; ISignatureValidator public immutable DEFAULT_SIG_VALIDATOR; + address public immutable NODE; + // TODO: estimate these values better uint32 public constant MIN_CHALLENGE_DURATION = 1 days; + uint32 public constant MAX_CHALLENGE_DURATION = 7 days; uint32 public constant ESCROW_DEPOSIT_UNLOCK_DELAY = 3 hours; - // NOTE: this value should not be small, so that as much escrow deposits as possible can be purged in one tx - // but also not too large, to avoid hitting block gas limit during purge and incurring Denial-Of-Service attacks - uint32 public constant MAX_DEPOSIT_ESCROW_PURGE = 64; + // NOTE: bounds the total number of queue entries inspected per purge call (including FINALIZED/DISPUTED entries + // that are skipped). Every loop iteration counts against this limit, so per-call gas cost is always bounded + // regardless of how large a FINALIZED prefix has accumulated in front of the first live entry. + uint32 public constant MAX_DEPOSIT_ESCROW_STEPS = 64; // Gas limit for outbound transfers to prevent gas depletion attacks // Sufficient for: ETH transfers to smart wallets (6k-9k gas), ERC20 standard transfers (~50k gas), // ERC777 hooks (~2.6k registry lookup + <5k hook execution) uint256 public constant TRANSFER_GAS_LIMIT = 100000; + // Delay between validator registration and first use. + // Gives users a window to observe a newly registered validator on-chain and revoke + // ERC20 approvals before a compromised NODE key can weaponise it to forge user signatures. + uint64 public constant VALIDATOR_ACTIVATION_DELAY = 1 days; + mapping(bytes32 channelId => ChannelMeta meta) internal _channels; mapping(address user => EnumerableSet.Bytes32Set channelIds) internal _userChannels; @@ -159,12 +222,11 @@ contract ChannelHub is IVault, ReentrancyGuard { mapping(bytes32 escrowId => EscrowWithdrawalMeta meta) internal _escrowWithdrawals; - mapping(address node => mapping(address token => uint256 balance)) internal _nodeBalances; + mapping(address token => uint256 balance) internal _nodeBalances; // 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(uint8 validatorId => NodeValidator) internal _validatorRegistry; // Reclaim balances for failed outbound transfers // Accumulates funds when transfers fail (blacklists, hooks, gas depletion) @@ -173,19 +235,26 @@ contract ChannelHub is IVault, ReentrancyGuard { // ========== Constructor ========== - constructor(ISignatureValidator _defaultSigValidator) { + constructor(ISignatureValidator _defaultSigValidator, address _node) { require(address(_defaultSigValidator) != address(0), InvalidAddress()); + require(_node != address(0), InvalidAddress()); DEFAULT_SIG_VALIDATOR = _defaultSigValidator; + NODE = _node; } // ========== Getters ========== - function getAccountBalance(address node, address token) external view returns (uint256) { - return _nodeBalances[node][token]; + function getNodeBalance(address token) external view returns (uint256) { + return _nodeBalances[token]; } - function getNodeValidator(address node, uint8 validatorId) external view returns (ISignatureValidator) { - return _nodeValidatorRegistry[node][validatorId]; + function getNodeValidator(uint8 validatorId) + external + view + returns (ISignatureValidator validator, uint64 registeredAt) + { + NodeValidator memory validatorInfo = _validatorRegistry[validatorId]; + return (validatorInfo.validator, validatorInfo.registeredAt); } function getChannelIds(address user) external view returns (bytes32[] memory) { @@ -275,31 +344,40 @@ contract ChannelHub is IVault, ReentrancyGuard { return _reclaims[account][token]; } - // ========= IVault ========== + // ========= Node ========== - function depositToVault(address node, address token, uint256 amount) external payable { - require(node != address(0), InvalidAddress()); + // Intentionally CIE (Checks-Interactions-Effects): pull funds before updating accounting. + // Deposit state must only reflect tokens that have actually arrived — updating first would + // inflate _nodeBalances during ERC777/hook callbacks, enabling read-only reentrancy for + // external protocols querying getNodeBalance(). Contrast with withdrawFromNode, which uses + // CEI (decrement before push) to prevent re-entrancy drains. + function depositToNode(address token, uint256 amount) external payable { require(amount > 0, IncorrectAmount()); - _nodeBalances[node][token] += amount; - _pullFunds(msg.sender, token, amount); - emit Deposited(node, token, amount); + uint256 updatedBalance = _nodeBalances[token] + amount; + _nodeBalances[token] = updatedBalance; + + emit Deposited(token, amount); + emit NodeBalanceUpdated(token, updatedBalance); } - function withdrawFromVault(address to, address token, uint256 amount) external { + function withdrawFromNode(address to, address token, uint256 amount) external { require(to != address(0), InvalidAddress()); require(amount > 0, IncorrectAmount()); + require(msg.sender == NODE, IncorrectMsgSender()); - uint256 currentBalance = _nodeBalances[msg.sender][token]; + uint256 currentBalance = _nodeBalances[token]; require(currentBalance >= amount, InsufficientBalance()); - _nodeBalances[msg.sender][token] = currentBalance - amount; + uint256 updatedBalance = currentBalance - amount; + _nodeBalances[token] = updatedBalance; _pushFunds(to, token, amount); - emit Withdrawn(msg.sender, token, amount); + emit Withdrawn(token, amount); + emit NodeBalanceUpdated(token, updatedBalance); } /** @@ -329,7 +407,7 @@ contract ChannelHub is IVault, ReentrancyGuard { } // ========= Escrow Deposit Purge ========== - function getUnlockableEscrowDepositStats() internal view returns (uint256 count, uint256 totalAmount) { + function getUnlockableEscrowDepositStats() public view returns (uint256 count, uint256 totalAmount) { uint256 totalDeposits = _escrowDepositIds.length; uint256 escrowHeadTemp = escrowHead; @@ -337,6 +415,10 @@ contract ChannelHub is IVault, ReentrancyGuard { bytes32 escrowId = _escrowDepositIds[escrowHeadTemp]; EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; + if (_isEscrowDepositSkippable(meta)) { + escrowHeadTemp++; + continue; + } if (_isEscrowDepositUnlockable(meta)) { count++; totalAmount += meta.lockedAmount; @@ -363,35 +445,45 @@ contract ChannelHub is IVault, ReentrancyGuard { } } - function purgeEscrowDeposits(uint256 maxToPurge) external { - _purgeEscrowDeposits(maxToPurge); + function purgeEscrowDeposits(uint256 maxSteps) external { + _purgeEscrowDeposits(maxSteps); } function _purgeEscrowDeposits() internal { - _purgeEscrowDeposits(MAX_DEPOSIT_ESCROW_PURGE); + _purgeEscrowDeposits(MAX_DEPOSIT_ESCROW_STEPS); } - function _purgeEscrowDeposits(uint256 maxToPurge) internal { + function _purgeEscrowDeposits(uint256 maxSteps) internal { uint256 purgedCount = 0; + uint256 steps = 0; uint256 totalDeposits = _escrowDepositIds.length; uint256 escrowHeadTemp = escrowHead; - while (escrowHeadTemp < totalDeposits && purgedCount < maxToPurge) { + uint256 remaining = totalDeposits > escrowHeadTemp ? totalDeposits - escrowHeadTemp : 0; + uint256 maxPossible = maxSteps < remaining ? maxSteps : remaining; + bytes32[] memory purgedIds = new bytes32[](maxPossible); + + while (escrowHeadTemp < totalDeposits && steps < maxSteps) { bytes32 escrowId = _escrowDepositIds[escrowHeadTemp]; EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; + steps++; - // Skip already-finalized escrows so they don't block the queue - if (meta.status == EscrowStatus.FINALIZED) { + if (_isEscrowDepositSkippable(meta)) { escrowHeadTemp++; continue; } - // Only INITIALIZED escrows can be purged; CHALLENGED escrows require manual finalization if (_isEscrowDepositUnlockable(meta)) { - _nodeBalances[meta.node][meta.initState.nonHomeLedger.token] += meta.lockedAmount; + address token = meta.initState.nonHomeLedger.token; + uint256 updatedBalance = _nodeBalances[token] + meta.lockedAmount; + _nodeBalances[token] = updatedBalance; + meta.status = EscrowStatus.FINALIZED; meta.lockedAmount = 0; + purgedIds[purgedCount] = escrowId; purgedCount++; escrowHeadTemp++; + + emit NodeBalanceUpdated(token, updatedBalance); } else { break; } @@ -400,47 +492,55 @@ contract ChannelHub is IVault, ReentrancyGuard { escrowHead = escrowHeadTemp; if (purgedCount != 0) { - emit EscrowDepositsPurged(purgedCount); + // Trim the over-allocated memory array to the actual purged count. + assembly { + mstore(purgedIds, purgedCount) + } + emit EscrowDepositsPurged(purgedIds, purgedCount); } } - /// @dev Check if an escrow deposit can be unlocked + /// @dev Check if an escrow deposit can be unlocked (INITIALIZED and past unlock time) function _isEscrowDepositUnlockable(EscrowDepositMeta storage meta) internal view returns (bool) { return meta.unlockAt <= block.timestamp && meta.status == EscrowStatus.INITIALIZED; } + /// @dev Check if an escrow deposit should be skipped without action (FINALIZED or DISPUTED) + function _isEscrowDepositSkippable(EscrowDepositMeta storage meta) internal view returns (bool) { + return meta.status == EscrowStatus.FINALIZED || meta.status == EscrowStatus.DISPUTED; + } + // ========= Validator Registry ========== /** - * @notice Register a signature validator for a node using signature-based authorization - * @dev Anyone can submit this transaction with a valid node signature, enabling relayer-friendly registration. - * The node's private key only signs the registration data, never sends transactions directly. - * This allows nodes to use cold storage or HSMs without exposing keys to transaction submission. - * The signature includes block.chainid to prevent cross-chain replay attacks. - * @param node The node address that signed the registration + * @notice Register a signature validator for NODE using signature-based authorization + * @dev Anyone can submit this transaction with a valid NODE signature, enabling relayer-friendly registration. + * For this function only, NODE's private key only signs the registration data and does not need to send + * the transaction directly, allowing cold storage or HSM usage without exposing keys to transaction submission. + * Other NODE-gated operations (withdrawFromNode, home-chain initiateEscrowDeposit, and submitting + * a new INITIATE_ESCROW_DEPOSIT state via challengeChannel) still require NODE to be msg.sender + * and are not relayer-compatible. Note: challenging with the same-version INITIATE_ESCROW_DEPOSIT + * state (already-processed path) is open to any caller, as it only sets the DISPUTED flag. + * The signature includes block.chainid and address(this) to prevent cross-chain and cross-deployment replay attacks. * @param validatorId The validator ID (0x01-0xFF, 0x00 reserved for DEFAULT) * @param validator The validator contract address - * @param signature Node's signature over abi.encode(validatorId, validator, block.chainid) + * @param signature NODE's signature over abi.encode(block.chainid, address(this), validatorId, validator) */ - function registerNodeValidator( - address node, - uint8 validatorId, - ISignatureValidator validator, - bytes calldata signature - ) external { + function registerNodeValidator(uint8 validatorId, ISignatureValidator validator, bytes calldata signature) + external + { require(validatorId != DEFAULT_SIG_VALIDATOR_ID, InvalidValidatorId()); require(address(validator) != address(0), InvalidAddress()); require( - address(_nodeValidatorRegistry[node][validatorId]) == address(0), - ValidatorAlreadyRegistered(node, validatorId) + address(_validatorRegistry[validatorId].validator) == address(0), ValidatorAlreadyRegistered(validatorId) ); - bytes memory message = abi.encode(validatorId, validator, block.chainid); - require(EcdsaSignatureUtils.validateEcdsaSigner(message, signature, node), IncorrectSignature()); + bytes memory message = Utils.getValidatorRegistrationMessage(address(this), validatorId, address(validator)); + require(EcdsaSignatureUtils.validateEcdsaSigner(message, signature, NODE), IncorrectSignature()); - _nodeValidatorRegistry[node][validatorId] = validator; + _validatorRegistry[validatorId] = NodeValidator({validator: validator, registeredAt: uint64(block.timestamp)}); - emit ValidatorRegistered(node, validatorId, validator); + emit ValidatorRegistered(validatorId, validator); } // ========== Channel lifecycle ========== @@ -448,24 +548,31 @@ contract ChannelHub is IVault, ReentrancyGuard { // Create channel with DEPOSIT, WITHDRAW, or OPERATE intent // This enables users who already have off-chain virtual states with non-zero version // to create a channel and perform initial operation simultaneously + // NOTE: For native ETH channels with DEPOSIT intent, msg.sender must supply msg.value == deposit amount. function createChannel(ChannelDefinition calldata def, State calldata initState) external payable { require( initState.intent == StateIntent.DEPOSIT || initState.intent == StateIntent.WITHDRAW || initState.intent == StateIntent.OPERATE, IncorrectStateIntent() ); + if (initState.intent != StateIntent.DEPOSIT) { + require(msg.value == 0, IncorrectValue()); + } bytes32 channelId = Utils.getChannelId(def, VERSION); + require(_channels[channelId].status == ChannelStatus.VOID, IncorrectChannelStatus()); + + address user = def.user; _requireValidDefinition(def); - _validateSignatures(channelId, initState, def.user, def.node, def.approvedSignatureValidators); + _validateSignatures(channelId, initState, user, def.approvedSignatureValidators); ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][initState.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[initState.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, initState); _applyEffects(channelId, def, initState, effects); - _userChannels[def.user].add(channelId); + _userChannels[user].add(channelId); // Emit appropriate event based on intent if (initState.intent == StateIntent.DEPOSIT) { @@ -476,18 +583,18 @@ contract ChannelHub is IVault, ReentrancyGuard { emit ChannelCheckpointed(channelId, initState); } - emit ChannelCreated(channelId, def.user, def.node, def, initState); + emit ChannelCreated(channelId, user, def, initState); } + // NOTE: For native ETH channels, msg.sender must supply msg.value == deposit amount. 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; - _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); + ChannelDefinition memory def = _channels[channelId].definition; + _validateSignatures(channelId, candidate, def.user, def.approvedSignatureValidators); ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[candidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -495,15 +602,14 @@ contract ChannelHub is IVault, ReentrancyGuard { emit ChannelDeposited(channelId, candidate); } - function withdrawFromChannel(bytes32 channelId, State calldata candidate) public payable { + function withdrawFromChannel(bytes32 channelId, State calldata candidate) public { require(candidate.intent == StateIntent.WITHDRAW, IncorrectStateIntent()); - ChannelMeta storage meta = _channels[channelId]; - ChannelDefinition memory def = meta.definition; - _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); + ChannelDefinition memory def = _channels[channelId].definition; + _validateSignatures(channelId, candidate, def.user, def.approvedSignatureValidators); ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[candidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -511,15 +617,14 @@ contract ChannelHub is IVault, ReentrancyGuard { emit ChannelWithdrawn(channelId, candidate); } - function checkpointChannel(bytes32 channelId, State calldata candidate) external payable { + function checkpointChannel(bytes32 channelId, State calldata candidate) external { require(candidate.intent == StateIntent.OPERATE, IncorrectStateIntent()); // Can only checkpoint operate states - ChannelMeta storage meta = _channels[channelId]; - ChannelDefinition memory def = meta.definition; - _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); + ChannelDefinition memory def = _channels[channelId].definition; + _validateSignatures(channelId, candidate, def.user, def.approvedSignatureValidators); ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[candidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -543,23 +648,36 @@ contract ChannelHub is IVault, ReentrancyGuard { require(candidate.version >= prevState.version, ChallengerVersionTooLow()); address user = def.user; - address node = def.node; + uint256 approvedSignatureValidators = def.approvedSignatureValidators; // If version is higher, process the new state if (candidate.version > prevState.version) { - _validateSignatures(channelId, candidate, user, node, def.approvedSignatureValidators); + // Cannot challenge with a CLOSE intent, use `closeChannel(...)` function instead + require(candidate.intent != StateIntent.CLOSE, IncorrectStateIntent()); + // Cannot challenge with a FINALIZE_MIGRATION intent on an old home channel, use `finalizeMigration(...)` function instead + require( + !(status == ChannelStatus.OPERATING && candidate.intent == StateIntent.FINALIZE_MIGRATION), + IncorrectStateIntent() + ); + // INITIATE_ESCROW_DEPOSIT on the home chain may only be submitted by the node + require( + !(candidate.intent == StateIntent.INITIATE_ESCROW_DEPOSIT && msg.sender != NODE), IncorrectMsgSender() + ); + + _validateSignatures(channelId, candidate, user, approvedSignatureValidators); ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[node][candidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[candidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyTransitionEffects(channelId, def, candidate, effects); + } else { + require(msg.value == 0, IncorrectValue()); } - // else: challenging with same version, state already processed (ISignatureValidator validator, bytes calldata sigData) = - _extractValidator(challengerSig, node, def.approvedSignatureValidators); - _validateChallengerSignature(channelId, candidate, sigData, validator, user, node, challengerIdx); + _extractValidator(challengerSig, approvedSignatureValidators); + _validateChallengerSignature(channelId, candidate, sigData, validator, user, challengerIdx); meta.status = ChannelStatus.DISPUTED; uint64 challengeExpiry = uint64(block.timestamp) + def.challengeDuration; @@ -568,23 +686,22 @@ contract ChannelHub is IVault, ReentrancyGuard { emit ChannelChallenged(channelId, candidate, challengeExpiry); } - function closeChannel(bytes32 channelId, State calldata candidate) external payable { + function closeChannel(bytes32 channelId, State calldata candidate) external { ChannelMeta storage meta = _channels[channelId]; ChannelDefinition memory def = meta.definition; ChannelStatus status = meta.status; State memory prevState = meta.lastState; - address node = def.node; 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); + _nonRevertingPushFunds(user, prevState.homeLedger.token, prevState.homeLedger.userAllocation); + _nonRevertingPushFunds(NODE, prevState.homeLedger.token, prevState.homeLedger.nodeAllocation); _userChannels[user].remove(channelId); @@ -594,10 +711,10 @@ 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); + _validateSignatures(channelId, candidate, user, def.approvedSignatureValidators); ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[candidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -608,26 +725,30 @@ contract ChannelHub is IVault, ReentrancyGuard { // ========= Cross-Chain Functions ========== + // NOTE: On non-home chain, user funds are pulled. For native ETH, msg.sender must supply msg.value == deposit amount. function initiateEscrowDeposit(ChannelDefinition calldata def, State calldata candidate) external payable { require(candidate.intent == StateIntent.INITIATE_ESCROW_DEPOSIT, IncorrectStateIntent()); + _requireValidDefinition(def); bytes32 channelId = Utils.getChannelId(def, VERSION); - _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); + address user = def.user; + uint256 approvedSignatureValidators = def.approvedSignatureValidators; + _validateSignatures(channelId, candidate, user, approvedSignatureValidators); bytes32 escrowId = Utils.getEscrowId(channelId, candidate.version); - if (_isHomeChain(channelId)) { - _processHomeChainEscrowInitiate(channelId, candidate); + if (_isChannelHomeChain(channelId)) { + require(msg.sender == NODE, IncorrectMsgSender()); + require(msg.value == 0, IncorrectValue()); + _processHomeChainEscrow(channelId, candidate); emit EscrowDepositInitiatedOnHome(escrowId, channelId, candidate); } else { // NON-HOME CHAIN: Create escrow record - recover addresses from signatures - EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId, 0); + EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId); EscrowDepositEngine.TransitionEffects memory effects = EscrowDepositEngine.validateTransition(ctx, candidate); - _applyEscrowDepositEffects( - escrowId, channelId, candidate, effects, def.user, def.node, def.approvedSignatureValidators - ); + _applyEscrowDepositEffects(escrowId, channelId, candidate, effects, user, approvedSignatureValidators); _escrowDepositIds.push(escrowId); emit EscrowDepositInitiated(escrowId, channelId, candidate); @@ -639,29 +760,32 @@ contract ChannelHub is IVault, ReentrancyGuard { { EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; bytes32 channelId = meta.channelId; - require(channelId != bytes32(0), NoChannelIdFound()); + require(channelId != bytes32(0), NoChannelIdFoundForEscrow()); + + address user = meta.user; + uint256 approvedSignatureValidators = meta.approvedSignatureValidators; (ISignatureValidator validator, bytes calldata sigData) = - _extractValidator(challengerSig, meta.node, meta.approvedSignatureValidators); - _validateChallengerSignature(channelId, meta.initState, sigData, validator, meta.user, meta.node, challengerIdx); + _extractValidator(challengerSig, approvedSignatureValidators); + _validateChallengerSignature(channelId, meta.initState, sigData, validator, user, challengerIdx); - EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId, 0); + EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId); EscrowDepositEngine.TransitionEffects memory effects = EscrowDepositEngine.validateChallenge(ctx); - _applyEscrowDepositEffects( - escrowId, channelId, meta.initState, effects, meta.user, meta.node, meta.approvedSignatureValidators - ); + _applyEscrowDepositEffects(escrowId, channelId, meta.initState, effects, user, approvedSignatureValidators); emit EscrowDepositChallenged(escrowId, meta.initState, effects.newChallengeExpiry); } 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 - ); + if (_isEscrowDepositHomeChain(channelId, escrowId)) { + // HOME CHAIN: Get user from channel definition + require(candidate.intent == StateIntent.FINALIZE_ESCROW_DEPOSIT, IncorrectStateIntent()); + + ChannelDefinition storage metaDef = _channels[channelId].definition; + _validateSignatures(channelId, candidate, metaDef.user, metaDef.approvedSignatureValidators); + + _processHomeChainEscrow(channelId, candidate); emit EscrowDepositFinalizedOnHome(escrowId, channelId, candidate); return; } @@ -670,59 +794,63 @@ contract ChannelHub is IVault, ReentrancyGuard { EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; require(meta.channelId == channelId, IncorrectChannelId()); // Validate consistency address user = meta.user; - address node = meta.node; EscrowStatus status = meta.status; - if (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) + _nonRevertingPushFunds(user, meta.initState.nonHomeLedger.token, lockedAmount); + + // Eagerly advance the queue head so FINALIZED entries don't accumulate + _purgeEscrowDeposits(); - emit EscrowDepositFinalized(escrowId, channelId, candidate); + emit EscrowDepositFinalized(escrowId, channelId, meta.initState); return; } require(candidate.intent == StateIntent.FINALIZE_ESCROW_DEPOSIT, IncorrectStateIntent()); + uint256 approvedSignatureValidators = meta.approvedSignatureValidators; + // NON-HOME CHAIN: Update via EscrowDepositEngine - _validateSignatures(channelId, candidate, user, node, meta.approvedSignatureValidators); + _validateSignatures(channelId, candidate, user, approvedSignatureValidators); - EscrowDepositEngine.TransitionContext memory ctx = - _buildEscrowDepositContext(escrowId, _nodeBalances[node][candidate.nonHomeLedger.token]); + EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId); EscrowDepositEngine.TransitionEffects memory effects = EscrowDepositEngine.validateTransition(ctx, candidate); - _applyEscrowDepositEffects( - escrowId, channelId, candidate, effects, user, node, meta.approvedSignatureValidators - ); + _applyEscrowDepositEffects(escrowId, channelId, candidate, effects, user, approvedSignatureValidators); emit EscrowDepositFinalized(escrowId, channelId, candidate); } function initiateEscrowWithdrawal(ChannelDefinition calldata def, State calldata candidate) external { require(candidate.intent == StateIntent.INITIATE_ESCROW_WITHDRAWAL, IncorrectStateIntent()); + _requireValidDefinition(def); bytes32 channelId = Utils.getChannelId(def, VERSION); - _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); + address user = def.user; + uint256 approvedSignatureValidators = def.approvedSignatureValidators; + _validateSignatures(channelId, candidate, user, approvedSignatureValidators); bytes32 escrowId = Utils.getEscrowId(channelId, candidate.version); - if (_isHomeChain(channelId)) { + if (_isChannelHomeChain(channelId)) { // HOME CHAIN: Process through channel state, no escrow metadata - _processHomeChainEscrowInitiate(channelId, candidate); + _processHomeChainEscrow(channelId, candidate); emit EscrowWithdrawalInitiatedOnHome(escrowId, channelId, candidate); } else { // NON-HOME CHAIN - EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, def.node); + EscrowWithdrawalEngine.TransitionContext memory ctx = + _buildEscrowWithdrawalContext(escrowId, _nodeBalances[candidate.nonHomeLedger.token]); EscrowWithdrawalEngine.TransitionEffects memory effects = EscrowWithdrawalEngine.validateTransition(ctx, candidate); - _applyEscrowWithdrawalEffects( - escrowId, channelId, candidate, effects, def.user, def.node, def.approvedSignatureValidators - ); + _applyEscrowWithdrawalEffects(escrowId, channelId, candidate, effects, user, approvedSignatureValidators); emit EscrowWithdrawalInitiated(escrowId, channelId, candidate); } @@ -733,32 +861,32 @@ contract ChannelHub is IVault, ReentrancyGuard { { EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; bytes32 channelId = meta.channelId; - require(channelId != bytes32(0), NoChannelIdFound()); + require(channelId != bytes32(0), NoChannelIdFoundForEscrow()); - EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, meta.node); + EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, 0); EscrowWithdrawalEngine.TransitionEffects memory effects = EscrowWithdrawalEngine.validateChallenge(ctx); // Validate challenger signature address user = meta.user; - address node = meta.node; + uint256 approvedSignatureValidators = meta.approvedSignatureValidators; (ISignatureValidator validator, bytes calldata sigData) = - _extractValidator(challengerSig, node, meta.approvedSignatureValidators); - _validateChallengerSignature(channelId, meta.initState, sigData, validator, user, node, challengerIdx); + _extractValidator(challengerSig, approvedSignatureValidators); + _validateChallengerSignature(channelId, meta.initState, sigData, validator, user, challengerIdx); - _applyEscrowWithdrawalEffects( - escrowId, channelId, meta.initState, effects, user, node, meta.approvedSignatureValidators - ); + _applyEscrowWithdrawalEffects(escrowId, channelId, meta.initState, effects, user, approvedSignatureValidators); emit EscrowWithdrawalChallenged(escrowId, meta.initState, effects.newChallengeExpiry); } 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 - ); + if (_isEscrowWithdrawalHomeChain(channelId, escrowId)) { + // HOME CHAIN: Get user from channel definition + require(candidate.intent == StateIntent.FINALIZE_ESCROW_WITHDRAWAL, IncorrectStateIntent()); + + ChannelDefinition storage metaDef = _channels[channelId].definition; + _validateSignatures(channelId, candidate, metaDef.user, metaDef.approvedSignatureValidators); + + _processHomeChainEscrow(channelId, candidate); emit EscrowWithdrawalFinalizedOnHome(escrowId, channelId, candidate); return; } @@ -767,34 +895,40 @@ contract ChannelHub is IVault, ReentrancyGuard { EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; require(meta.channelId == channelId, IncorrectChannelId()); // Validate consistency address user = meta.user; - address node = meta.node; EscrowStatus status = meta.status; - if (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[withdrawalToken] + lockedAmount; + _nodeBalances[withdrawalToken] = updatedWithdrawalBalance; + + emit NodeBalanceUpdated(withdrawalToken, updatedWithdrawalBalance); - emit EscrowWithdrawalFinalized(escrowId, channelId, candidate); + // Eagerly advance the queue head so FINALIZED entries don't accumulate + _purgeEscrowDeposits(); + + emit EscrowWithdrawalFinalized(escrowId, channelId, meta.initState); return; } require(candidate.intent == StateIntent.FINALIZE_ESCROW_WITHDRAWAL, IncorrectStateIntent()); // NON-HOME CHAIN: Update via EscrowWithdrawalEngine - _validateSignatures(channelId, candidate, user, node, meta.approvedSignatureValidators); + uint256 approvedSignatureValidators = meta.approvedSignatureValidators; + _validateSignatures(channelId, candidate, user, approvedSignatureValidators); - EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, node); + EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, 0); EscrowWithdrawalEngine.TransitionEffects memory effects = EscrowWithdrawalEngine.validateTransition(ctx, candidate); - _applyEscrowWithdrawalEffects( - escrowId, channelId, candidate, effects, user, node, meta.approvedSignatureValidators - ); + _applyEscrowWithdrawalEffects(escrowId, channelId, candidate, effects, user, approvedSignatureValidators); emit EscrowWithdrawalFinalized(escrowId, channelId, candidate); } @@ -803,10 +937,11 @@ contract ChannelHub is IVault, ReentrancyGuard { require(candidate.intent == StateIntent.INITIATE_MIGRATION, IncorrectStateIntent()); bytes32 channelId = Utils.getChannelId(def, VERSION); - _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); + address user = def.user; + _validateSignatures(channelId, candidate, user, def.approvedSignatureValidators); State memory targetCandidate = candidate; - bool isHomeChain = _isHomeChain(channelId); + bool isHomeChain = _isChannelHomeChain(channelId); if (!isHomeChain) { // Initiate migration IN (on new home chain) @@ -818,11 +953,11 @@ contract ChannelHub is IVault, ReentrancyGuard { targetCandidate.userSig = ""; // Invalidate signatures after swap targetCandidate.nodeSig = ""; - _userChannels[def.user].add(channelId); + _userChannels[user].add(channelId); } ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][targetCandidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[targetCandidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, targetCandidate); _applyEffects(channelId, def, targetCandidate, effects); @@ -836,17 +971,16 @@ contract ChannelHub is IVault, ReentrancyGuard { function finalizeMigration(bytes32 channelId, State calldata candidate) external { require(candidate.intent == StateIntent.FINALIZE_MIGRATION, IncorrectStateIntent()); - ChannelMeta storage meta = _channels[channelId]; - ChannelDefinition memory def = meta.definition; + ChannelDefinition memory def = _channels[channelId].definition; address user = def.user; - _validateSignatures(channelId, candidate, user, def.node, def.approvedSignatureValidators); + _validateSignatures(channelId, candidate, user, def.approvedSignatureValidators); State memory targetCandidate = candidate; // `_isHomeChain(...)` cannot be used here as channel exists on both chains - bool isHomeChain = candidate.nonHomeLedger.chainId == block.chainid; + bool isOldHomeChain = candidate.nonHomeLedger.chainId == block.chainid; - if (isHomeChain) { + if (isOldHomeChain) { // Finalize migration OUT (on old home chain) // Swap states before validation to maintain invariant, so that homeLedger = current chain targetCandidate.homeLedger = candidate.nonHomeLedger; @@ -858,11 +992,11 @@ contract ChannelHub is IVault, ReentrancyGuard { } ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][targetCandidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[targetCandidate.homeLedger.token]); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, targetCandidate); _applyEffects(channelId, def, targetCandidate, effects); - if (isHomeChain) { + if (isOldHomeChain) { emit MigrationOutFinalized(channelId, candidate); } else { emit MigrationInFinalized(channelId, candidate); @@ -875,15 +1009,14 @@ contract ChannelHub is IVault, ReentrancyGuard { bytes32 channelId, State calldata state, address user, - address node, uint256 approvedSignatureValidators ) internal view { (ISignatureValidator userValidator, bytes calldata userSigData) = - _extractValidator(state.userSig, node, approvedSignatureValidators); + _extractValidator(state.userSig, approvedSignatureValidators); _validateSignature(channelId, state, userSigData, user, userValidator); (ISignatureValidator nodeValidator, bytes calldata nodeSigData) = - _extractValidator(state.nodeSig, node, approvedSignatureValidators); - _validateSignature(channelId, state, nodeSigData, node, nodeValidator); + _extractValidator(state.nodeSig, approvedSignatureValidators); + _validateSignature(channelId, state, nodeSigData, NODE, nodeValidator); } /** @@ -906,7 +1039,7 @@ contract ChannelHub is IVault, ReentrancyGuard { require(ValidationResult.unwrap(result) != ValidationResult.unwrap(VALIDATION_FAILURE), IncorrectSignature()); } - function _extractValidator(bytes calldata signature, address node, uint256 approvedSignatureValidators) + function _extractValidator(bytes calldata signature, uint256 approvedSignatureValidators) internal view returns (ISignatureValidator validator, bytes calldata sigData) @@ -921,8 +1054,14 @@ contract ChannelHub is IVault, ReentrancyGuard { } else { // Look up validator in node's registry require((approvedSignatureValidators >> validatorId) & 1 == 1, ValidatorNotApproved()); - validator = _nodeValidatorRegistry[node][validatorId]; - require(address(validator) != address(0), ValidatorNotRegistered(node, validatorId)); + + NodeValidator storage entry = _validatorRegistry[validatorId]; + require(address(entry.validator) != address(0), ValidatorNotRegistered(validatorId)); + + uint64 activatesAt = entry.registeredAt + VALIDATOR_ACTIVATION_DELAY; + require(block.timestamp >= activatesAt, ValidatorNotActive(validatorId, activatesAt)); + + validator = entry.validator; } sigData = _sliceCalldata(signature, 1); @@ -942,7 +1081,6 @@ contract ChannelHub is IVault, ReentrancyGuard { * @param sigData The challenger's signature data (without validator ID byte) * @param validator The validator to use for verification * @param user The user's address - * @param node The node's address */ function _validateChallengerSignature( bytes32 channelId, @@ -950,44 +1088,26 @@ contract ChannelHub is IVault, ReentrancyGuard { bytes calldata sigData, ISignatureValidator validator, address user, - address node, ParticipantIndex challengerIdx ) internal view { bytes memory signingData = Utils.toSigningData(state); - bytes memory challengerSigningData = abi.encodePacked(signingData, "challenge"); - address challenger = challengerIdx == ParticipantIndex.USER ? user : node; - ValidationResult result = validator.validateSignature(channelId, challengerSigningData, sigData, challenger); + address challenger = challengerIdx == ParticipantIndex.USER ? user : NODE; + ValidationResult result = validator.validateChallengeSignature(channelId, signingData, sigData, challenger); require(ValidationResult.unwrap(result) != ValidationResult.unwrap(VALIDATION_FAILURE), IncorrectSignature()); } - /// @dev Process HOME CHAIN path for escrow initiate operations - function _processHomeChainEscrowInitiate(bytes32 channelId, State calldata candidate) internal { - ChannelMeta storage meta = _channels[channelId]; - ChannelDefinition memory metaDef = meta.definition; + /// @dev Process HOME CHAIN path for escrow operations + function _processHomeChainEscrow(bytes32 channelId, State calldata candidate) internal { + ChannelDefinition memory metaDef = _channels[channelId].definition; ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[metaDef.node][candidate.homeLedger.token]); + _buildChannelContext(channelId, _nodeBalances[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 - { - 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.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); - - _applyEffects(channelId, channelDef, candidate, effects); - } - - function _buildChannelContext(bytes32 channelId, uint256 nodeBalance) + function _buildChannelContext(bytes32 channelId, uint256 nodeAvailableFunds) internal view returns (ChannelEngine.TransitionContext memory ctx) @@ -997,13 +1117,13 @@ contract ChannelHub is IVault, ReentrancyGuard { ctx.status = meta.status; ctx.prevState = meta.lastState; ctx.lockedFunds = meta.lockedFunds; - ctx.nodeAvailableFunds = nodeBalance; + ctx.nodeAvailableFunds = nodeAvailableFunds; ctx.challengeExpiry = meta.challengeExpireAt; return ctx; } - function _buildEscrowDepositContext(bytes32 escrowId, uint256 nodeAvailableFunds) + function _buildEscrowDepositContext(bytes32 escrowId) internal view returns (EscrowDepositEngine.TransitionContext memory ctx) @@ -1015,12 +1135,11 @@ contract ChannelHub is IVault, ReentrancyGuard { ctx.lockedAmount = meta.lockedAmount; ctx.unlockAt = meta.unlockAt; ctx.challengeExpiry = meta.challengeExpireAt; - ctx.nodeAvailableFunds = nodeAvailableFunds; return ctx; } - function _buildEscrowWithdrawalContext(bytes32 escrowId, address node) + function _buildEscrowWithdrawalContext(bytes32 escrowId, uint256 nodeAvailableFunds) internal view returns (EscrowWithdrawalEngine.TransitionContext memory ctx) @@ -1031,7 +1150,7 @@ contract ChannelHub is IVault, ReentrancyGuard { ctx.initState = meta.initState; ctx.lockedAmount = meta.lockedAmount; ctx.challengeExpiry = meta.challengeExpireAt; - ctx.nodeAddress = node; + ctx.nodeAvailableFunds = nodeAvailableFunds; return ctx; } @@ -1070,43 +1189,44 @@ contract ChannelHub is IVault, ReentrancyGuard { ChannelEngine.TransitionEffects memory effects ) internal { ChannelMeta storage meta = _channels[channelId]; + address token = candidate.homeLedger.token; - if (effects.updateLastState) { - meta.lastState = candidate; - } + meta.lastState = candidate; - address token = candidate.homeLedger.token; + address user = def.user; // 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(user, token, amount); meta.lockedFunds += amount; + } else { + require(msg.value == 0, IncorrectValue()); } if (effects.nodeFundsDelta > 0) { uint256 amount = effects.nodeFundsDelta.toUint256(); - _nodeBalances[def.node][token] -= amount; + uint256 updatedBalance = _nodeBalances[token] - amount; + _nodeBalances[token] = updatedBalance; meta.lockedFunds += amount; + + emit NodeBalanceUpdated(token, updatedBalance); } // Then process NEGATIVE deltas (subtractions from lockedFunds) if (effects.userFundsDelta < 0) { uint256 amount = (-effects.userFundsDelta).toUint256(); - _pushFunds(def.user, token, amount); + _nonRevertingPushFunds(user, token, amount); meta.lockedFunds -= amount; } if (effects.nodeFundsDelta < 0) { uint256 amount = (-effects.nodeFundsDelta).toUint256(); - _nodeBalances[def.node][token] += amount; + uint256 updatedBalance = _nodeBalances[token] + amount; + _nodeBalances[token] = updatedBalance; meta.lockedFunds -= amount; - } - // Special handling for CLOSE: push nodeAllocation directly to node address - if (effects.closeChannel && candidate.homeLedger.nodeAllocation > 0) { - _pushFunds(def.node, token, candidate.homeLedger.nodeAllocation); - meta.lockedFunds -= candidate.homeLedger.nodeAllocation; + emit NodeBalanceUpdated(token, updatedBalance); } // NOTE: purge escrow deposits to unlock unutilized node liquidity @@ -1119,50 +1239,54 @@ contract ChannelHub is IVault, ReentrancyGuard { State memory candidate, EscrowDepositEngine.TransitionEffects memory effects, address user, - address node, uint256 approvedSignatureValidators ) internal { EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; + address token = effects.updateInitState ? candidate.nonHomeLedger.token : meta.initState.nonHomeLedger.token; if (effects.newStatus != EscrowStatus.VOID) { meta.status = effects.newStatus; } if (effects.updateInitState) { - _initEscrowDepositMetadata(escrowId, channelId, candidate, user, node, approvedSignatureValidators); + _initEscrowDepositMetadata(escrowId, channelId, candidate, user, approvedSignatureValidators); } if (effects.newUnlockAt > 0) { meta.unlockAt = effects.newUnlockAt; } - if (effects.newChallengeExpiry > 0) { + if (meta.challengeExpireAt != effects.newChallengeExpiry) { meta.challengeExpireAt = effects.newChallengeExpiry; } - // Determine the correct token to use (from init state for finalization, from candidate for initiation) - address token = effects.updateInitState ? candidate.nonHomeLedger.token : meta.initState.nonHomeLedger.token; - // Handle user funds (positive = pull from user) if (effects.userFundsDelta > 0) { uint256 amount = effects.userFundsDelta.toUint256(); _pullFunds(user, token, amount); meta.lockedAmount += amount; - } else if (effects.userFundsDelta < 0) { - uint256 amount = (-effects.userFundsDelta).toUint256(); - _pushFunds(user, token, amount); - meta.lockedAmount -= amount; + } else { + require(msg.value == 0, IncorrectValue()); + if (effects.userFundsDelta < 0) { + uint256 amount = (-effects.userFundsDelta).toUint256(); + _nonRevertingPushFunds(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[token] - amount; + _nodeBalances[token] = updatedBalance; meta.lockedAmount += amount; + emit NodeBalanceUpdated(token, updatedBalance); } else if (effects.nodeFundsDelta < 0) { uint256 amount = (-effects.nodeFundsDelta).toUint256(); - _nodeBalances[node][token] += amount; + uint256 updatedBalance = _nodeBalances[token] + amount; + _nodeBalances[token] = updatedBalance; meta.lockedAmount -= amount; + emit NodeBalanceUpdated(token, updatedBalance); } // NOTE: purge escrow deposits to unlock unutilized node liquidity @@ -1175,7 +1299,6 @@ contract ChannelHub is IVault, ReentrancyGuard { State memory candidate, EscrowWithdrawalEngine.TransitionEffects memory effects, address user, - address node, uint256 approvedSignatureValidators ) internal { EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; @@ -1185,10 +1308,10 @@ contract ChannelHub is IVault, ReentrancyGuard { } if (effects.updateInitState) { - _initEscrowWithdrawalMetadata(escrowId, channelId, candidate, user, node, approvedSignatureValidators); + _initEscrowWithdrawalMetadata(escrowId, channelId, candidate, user, approvedSignatureValidators); } - if (effects.newChallengeExpiry > 0) { + if (meta.challengeExpireAt != effects.newChallengeExpiry) { meta.challengeExpireAt = effects.newChallengeExpiry; } @@ -1202,19 +1325,23 @@ contract ChannelHub is IVault, ReentrancyGuard { meta.lockedAmount += amount; } else if (effects.userFundsDelta < 0) { uint256 amount = (-effects.userFundsDelta).toUint256(); - _pushFunds(user, token, amount); + _nonRevertingPushFunds(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[token] - amount; + _nodeBalances[token] = updatedBalance; meta.lockedAmount += amount; + emit NodeBalanceUpdated(token, updatedBalance); } else if (effects.nodeFundsDelta < 0) { uint256 amount = (-effects.nodeFundsDelta).toUint256(); - _nodeBalances[node][token] += amount; + uint256 updatedBalance = _nodeBalances[token] + amount; + _nodeBalances[token] = updatedBalance; meta.lockedAmount -= amount; + emit NodeBalanceUpdated(token, updatedBalance); } // NOTE: purge escrow deposits to unlock unutilized node liquidity @@ -1226,14 +1353,12 @@ contract ChannelHub is IVault, ReentrancyGuard { 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; } @@ -1242,25 +1367,35 @@ contract ChannelHub is IVault, ReentrancyGuard { 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()); - require(def.user != def.node, AddressCollision(def.user)); - require(def.challengeDuration >= MIN_CHALLENGE_DURATION, IncorrectChallengeDuration()); + function _requireValidDefinition(ChannelDefinition calldata def) internal view { + address user = def.user; + + require(user != address(0), InvalidAddress()); + require(def.node == NODE, IncorrectNode()); + require(user != NODE, AddressCollision(user)); + require( + def.challengeDuration >= MIN_CHALLENGE_DURATION && def.challengeDuration <= MAX_CHALLENGE_DURATION, + IncorrectChallengeDuration() + ); } - function _isHomeChain(bytes32 channelId) internal view returns (bool) { + /// @dev Returns true when the channel is active and considers the current chain its home chain. + /// VOID and MIGRATED_OUT are excluded: no channel exists here yet, or the channel has moved away. + /// MIGRATING_IN is intentionally treated as home chain: initiateMigration() on the new home chain + /// swaps homeLedger <-> nonHomeLedger before storing, so lastState.homeLedger already represents + /// the current chain. ChannelEngine processes all subsequent operations (deposit, withdraw, + /// checkpoint, escrow, close) for MIGRATING_IN using home chain logic — the only thing pending is + /// a finalizeMigration() call to advance the status to OPERATING. + function _isChannelHomeChain(bytes32 channelId) internal view returns (bool) { ChannelStatus status = _channels[channelId].status; if (status == ChannelStatus.VOID || status == ChannelStatus.MIGRATED_OUT) { return false; @@ -1269,23 +1404,59 @@ contract ChannelHub is IVault, ReentrancyGuard { return _channels[channelId].lastState.homeLedger.chainId == block.chainid; } - function _pullFunds(address from, address token, uint256 amount) internal nonReentrant { - if (amount == 0) return; + /// @dev Returns true when a finalizeEscrowDeposit call should be routed through the home chain path. + /// Escrow metadata is only written on the non-home chain during initiateEscrowDeposit, so its + /// presence means the escrow must always follow the non-home path — even if _isChannelHomeChain() + /// later returns true because of a subsequent migration. + function _isEscrowDepositHomeChain(bytes32 channelId, bytes32 escrowId) internal view returns (bool) { + return _escrowDeposits[escrowId].channelId == bytes32(0) && _isChannelHomeChain(channelId); + } + /// @dev Returns true when a finalizeEscrowWithdrawal call should be routed through the home chain path. + /// Escrow metadata is only written on the non-home chain during initiateEscrowWithdrawal, so its + /// presence means the escrow must always follow the non-home path — even if _isChannelHomeChain() + /// later returns true because of a subsequent migration. + function _isEscrowWithdrawalHomeChain(bytes32 channelId, bytes32 escrowId) internal view returns (bool) { + return _escrowWithdrawals[escrowId].channelId == bytes32(0) && _isChannelHomeChain(channelId); + } + + /// @dev native token: requires msg.value == amount; ERC20: requires msg.value == 0. + function _requireMsgValueForPull(address token, uint256 amount) internal view { if (token == address(0)) { require(msg.value == amount, IncorrectValue()); } else { require(msg.value == 0, IncorrectValue()); } + } + + function _pullFunds(address from, address token, uint256 amount) internal nonReentrant { + if (amount == 0) return; + + _requireMsgValueForPull(token, amount); if (token != address(0)) { IERC20(token).safeTransferFrom(from, address(this), amount); } } + /// @dev Reverts if the transfer fails. Used in non-adversarial contexts where atomicity is required + /// (e.g. voluntary vault withdrawals where the caller controls the destination). function _pushFunds(address to, address token, uint256 amount) internal nonReentrant { if (amount == 0) return; + if (token == address(0)) { + (bool success,) = payable(to).call{value: amount}(""); + require(success, NativeTransferFailed(to, amount)); + } else { + IERC20(token).safeTransfer(to, amount); + } + } + + /// @dev Never reverts. On failure, accumulates funds in `_reclaims[to]` for later recovery via `claimFunds()`. + /// Used in adversarial contexts (e.g. channel settlement) where a reverting recipient must not block progress. + function _nonRevertingPushFunds(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}(""); @@ -1295,23 +1466,41 @@ contract ChannelHub is IVault, ReentrancyGuard { return; } } else { - // 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) { + if (!_trySafeTransfer(token, to, amount)) { _reclaims[to][token] += amount; emit TransferFailed(to, token, amount); } } } + + /// @dev Gas-capped variant of SafeERC20.trySafeTransfer. Forwards at most TRANSFER_GAS_LIMIT gas + /// to prevent depletion attacks from malicious ERC777/ERC1363 hooks. + /// Returns true if the transfer succeeded, false otherwise. + /// - no return value: success if token is a contract (handles no-return-value tokens like old USDT) + /// - explicit return value: decoded as uint256, non-zero treated as success. + /// Decoding as uint256 (not bool) avoids an abi.decode revert on non-canonical bool encodings + /// (e.g. a token returning 2), which would otherwise break _nonRevertingPushFunds' no-revert guarantee. + /// Uses assembly to write the call output directly to scratch space (0x00-0x1f). + /// That range is already within the memory expanded by the preceding abi.encodeCall(), so no + /// additional memory expansion occurs regardless of actual returndata size. + /// The high-level `(bool, bytes memory) = addr.call(...)` form copies ALL returndata into caller memory + /// at caller-gas expense — a malicious token returning 1 MB would exhaust gas before we reach the length check. + function _trySafeTransfer(address token, address to, uint256 amount) internal returns (bool) { + bytes memory callData = abi.encodeCall(IERC20.transfer, (to, amount)); + bool success; + uint256 rdsize; + uint256 retval; + + assembly ("memory-safe") { + // Output to scratch space (0x00-0x1f); returndatasize() still reflects the full returndata length. + success := call(TRANSFER_GAS_LIMIT, token, 0, add(callData, 0x20), mload(callData), 0x00, 0x20) + rdsize := returndatasize() + retval := mload(0x00) + } + + if (!success) return false; + if (rdsize == 0) return address(token).code.length > 0; + if (rdsize < 32) return false; + return retval != 0; + } } diff --git a/contracts/src/EscrowDepositEngine.sol b/contracts/src/EscrowDepositEngine.sol index cb3ca756e..e85182943 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(); @@ -42,6 +43,7 @@ library EscrowDepositEngine { error FundConservationOnInitiate(); error FundConservationOnFinalize(); error NodeFundsDeltaAndLockedAmountMismatch(); + error EscrowTokenMismatch(); // ========== Constants ========== @@ -56,7 +58,6 @@ library EscrowDepositEngine { uint256 lockedAmount; uint64 unlockAt; uint64 challengeExpiry; - uint256 nodeAvailableFunds; } struct TransitionEffects { @@ -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 ========== @@ -188,6 +194,7 @@ library EscrowDepositEngine { // Must be immediate successor require(candidate.version == ctx.initState.version + 1, IncorrectStateVersion()); + require(candidate.nonHomeLedger.token == ctx.initState.nonHomeLedger.token, EscrowTokenMismatch()); require(ctx.initState.intent == StateIntent.INITIATE_ESCROW_DEPOSIT, IncorrectStateIntent()); uint256 depositAmount = ctx.initState.nonHomeLedger.userAllocation; diff --git a/contracts/src/EscrowWithdrawalEngine.sol b/contracts/src/EscrowWithdrawalEngine.sol index b32cee4ab..5a3b56d82 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(); @@ -42,6 +43,8 @@ library EscrowWithdrawalEngine { error FundConservationOnInitiate(); error FundConservationOnFinalize(); error UserFundsDeltaAndLockedAmountMismatch(); + error EscrowTokenMismatch(); + error InsufficientNodeBalance(); // ========== Constants ========== @@ -54,7 +57,7 @@ library EscrowWithdrawalEngine { State initState; uint256 lockedAmount; uint64 challengeExpiry; - address nodeAddress; + uint256 nodeAvailableFunds; } struct TransitionEffects { @@ -124,6 +127,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 ========== @@ -184,6 +192,7 @@ library EscrowWithdrawalEngine { // Must be immediate successor require(candidate.version == ctx.initState.version + 1, IncorrectStateVersion()); + require(candidate.nonHomeLedger.token == ctx.initState.nonHomeLedger.token, EscrowTokenMismatch()); require(ctx.initState.intent == StateIntent.INITIATE_ESCROW_WITHDRAWAL, IncorrectStateIntent()); uint256 withdrawalAmount = ctx.initState.nonHomeLedger.nodeAllocation; @@ -233,6 +242,7 @@ library EscrowWithdrawalEngine { if (candidate.intent == StateIntent.INITIATE_ESCROW_WITHDRAWAL) { // On initiate: node funds locked (positive delta) require(totalDelta == effects.nodeFundsDelta, FundConservationOnInitiate()); + require(ctx.nodeAvailableFunds >= effects.nodeFundsDelta.toUint256(), InsufficientNodeBalance()); } else if (candidate.intent == StateIntent.FINALIZE_ESCROW_WITHDRAWAL) { // On finalize: user funds released (negative delta) require(totalDelta == effects.userFundsDelta, FundConservationOnFinalize()); diff --git a/contracts/src/Utils.sol b/contracts/src/Utils.sol index 6a8b01ad9..dbfd4cfc7 100644 --- a/contracts/src/Utils.sol +++ b/contracts/src/Utils.sol @@ -17,11 +17,12 @@ library Utils { error FailedToFetchDecimals(); function getChannelId(ChannelDefinition memory def, uint8 version) internal pure returns (bytes32 channelId) { - bytes32 baseId = keccak256(abi.encode(def)); - assembly ("memory-safe") { + // ChannelDefinition has 6 static fields × 32 bytes = 192 (0xC0) bytes in memory. + // Memory layout is identical to abi.encode for structs with only value types, so we + // hash the struct pointer directly, avoiding the abi.encode allocation. + let baseId := keccak256(def, 0xC0) // Store the version in the first byte (most significant byte) of the channelId - // Clear the first byte of baseId, then set it to version channelId := or( and(baseId, 0x00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff), shl(248, version) @@ -29,9 +30,25 @@ library Utils { } } - function getEscrowId(bytes32 channelId, uint64 version) internal pure returns (bytes32) { + function getEscrowId(bytes32 channelId, uint64 version) internal pure returns (bytes32 escrowId) { // "channelId, (state-)version" pair is unique as long as participants do not reuse versions - return keccak256(abi.encode(channelId, version)); + // Uses the 64-byte scratch space (0x00–0x3f) to avoid a heap allocation. + // Mask version to 64 bits: mstore writes the full 256-bit stack word, and Solidity does + // not guarantee that internal call arguments are cleaned for sub-256-bit types. Without + // the mask, dirty high bits would produce a hash that diverges from abi.encode(channelId, version). + assembly ("memory-safe") { + mstore(0x00, channelId) + mstore(0x20, and(version, 0xffffffffffffffff)) + escrowId := keccak256(0x00, 0x40) + } + } + + function getValidatorRegistrationMessage(address channelHub, uint8 validatorId, address validator) + internal + view + returns (bytes memory) + { + return abi.encode(block.chainid, channelHub, validatorId, validator); } // ========== State ========== @@ -63,7 +80,10 @@ library Utils { /** * @notice Validates that the ledger's decimals match the token contract's decimals - * @dev Only validates if on the same chain as the ledger + * @dev Only validates if on the same chain as the ledger. + * The token must implement IERC20Metadata.decimals(). If the call reverts, + * this function reverts with FailedToFetchDecimals. ERC-20 tokens that omit + * the optional decimals() method cannot be used in any protocol operation. * @param ledger The ledger to validate */ function validateTokenDecimals(Ledger memory ledger) internal view { diff --git a/contracts/src/interfaces/ISignatureValidator.sol b/contracts/src/interfaces/ISignatureValidator.sol index 2181aa6b1..4dbb6d58f 100644 --- a/contracts/src/interfaces/ISignatureValidator.sol +++ b/contracts/src/interfaces/ISignatureValidator.sol @@ -39,4 +39,22 @@ interface ISignatureValidator { bytes calldata signature, address participant ) external view returns (ValidationResult); + + /** + * @notice Validates a challenge signature + * @dev The validator constructs the challenge message internally (e.g., appending "challenge" + * and any validator-specific data to signingData). This allows each validator to define + * its own challenge signature format and enforce validator-specific constraints. + * @param channelId The channel identifier to be included in the signed message + * @param signingData The encoded state data + * @param signature The challenge signature to validate + * @param participant The expected challenger's address + * @return result ValidationResult indicating success or failure + */ + function validateChallengeSignature( + bytes32 channelId, + bytes calldata signingData, + bytes calldata signature, + address participant + ) external view returns (ValidationResult); } diff --git a/contracts/src/interfaces/IVault.sol b/contracts/src/interfaces/IVault.sol deleted file mode 100644 index f8fcaef24..000000000 --- a/contracts/src/interfaces/IVault.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.30; - -/** - * @title Deposit Interface - * @notice Interface for contracts that manage token deposits and withdrawals - * @dev Handles funds that can be allocated to state channels - */ -interface IVault { - /** - * @notice Emitted when tokens are deposited into the contract - * @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 deposited - */ - event Deposited(address indexed wallet, address indexed token, uint256 amount); - - /** - * @notice Emitted when tokens are withdrawn from the contract - * @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 withdrawn - */ - event Withdrawn(address indexed wallet, address indexed token, uint256 amount); - - /** - * @notice Gets the balances of multiple accounts for multiple tokens - * @dev Returns a 2D array where each inner array corresponds to the balances of the tokens for each account - * @param account Address of the account to check balance for - * @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); - - /** - * @notice Deposits tokens into the contract - * @dev For native tokens, the value should be sent with the transaction - * @param account 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 depositToVault(address account, address token, uint256 amount) external payable; - - /** - * @notice Withdraws tokens from the contract - * @dev Can only withdraw available (not locked in channels) funds - * @param account Address of the account to send tokens to - * @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; -} diff --git a/contracts/src/interfaces/Types.sol b/contracts/src/interfaces/Types.sol index 364411e6d..314230b6e 100644 --- a/contracts/src/interfaces/Types.sol +++ b/contracts/src/interfaces/Types.sol @@ -8,6 +8,9 @@ enum ParticipantIndex { NODE } +// INVARIANT: Utils.getChannelId hashes the raw memory layout of this struct using the +// hardcoded size 0xC0 (6 fields × 32 bytes). Adding or removing fields requires updating +// that constant; failing to do so silently produces wrong channelIds. struct ChannelDefinition { uint32 challengeDuration; address user; diff --git a/contracts/src/sigValidators/ECDSAValidator.sol b/contracts/src/sigValidators/ECDSAValidator.sol index 5bee7b456..5d83f3cf7 100644 --- a/contracts/src/sigValidators/ECDSAValidator.sol +++ b/contracts/src/sigValidators/ECDSAValidator.sol @@ -29,12 +29,11 @@ contract ECDSAValidator is ISignatureValidator { * @param participant The expected signer's address * @return result VALIDATION_SUCCESS if valid, VALIDATION_FAILURE otherwise */ - function validateSignature( - bytes32 channelId, - bytes calldata signingData, - bytes calldata signature, - address participant - ) external pure returns (ValidationResult) { + function validateSignature(bytes32 channelId, bytes memory signingData, bytes memory signature, address participant) + public + pure + returns (ValidationResult) + { require(channelId != bytes32(0), EmptyChannelId()); require(participant != address(0), InvalidSignerAddress()); @@ -45,4 +44,22 @@ contract ECDSAValidator is ISignatureValidator { return VALIDATION_FAILURE; } } + + /** + * @notice Validates a challenge signature + * @dev Appends "challenge" to the packed message and verifies the ECDSA signature. + * @param channelId The channel identifier to include in the signed message + * @param signingData The encoded state data (without channelId, signatures, or challenge suffix) + * @param signature The challenge signature to validate (format: [r: 32][s: 32][v: 1], 65 bytes) + * @param participant The expected challenger's address + * @return result VALIDATION_SUCCESS if valid, VALIDATION_FAILURE otherwise + */ + function validateChallengeSignature( + bytes32 channelId, + bytes calldata signingData, + bytes calldata signature, + address participant + ) external pure returns (ValidationResult) { + return validateSignature(channelId, abi.encodePacked(signingData, "challenge"), signature, participant); + } } diff --git a/contracts/src/sigValidators/SessionKeyValidator.sol b/contracts/src/sigValidators/SessionKeyValidator.sol index 0302e3cba..6daed4b8f 100644 --- a/contracts/src/sigValidators/SessionKeyValidator.sol +++ b/contracts/src/sigValidators/SessionKeyValidator.sol @@ -10,6 +10,10 @@ import { import {EcdsaSignatureUtils} from "./EcdsaSignatureUtils.sol"; import {Utils} from "../Utils.sol"; +/// @dev Type hash for `SessionKeyAuthorization`, included in the authorization payload to prevent +/// reuse of unrelated `abi.encode(address, bytes32)` signatures as session-key authorizations. +bytes32 constant SESSION_KEY_AUTH_TYPEHASH = keccak256("Nitrolite.SessionKey(address sessionKey,bytes32 metadataHash)"); + /** * @notice Authorization struct for delegating signing authority to a session key * @dev The participant signs this authorization to allow the session key to sign on their behalf @@ -25,6 +29,7 @@ struct SessionKeyAuthorization { function toSigningData(SessionKeyAuthorization memory skAuth) pure returns (bytes memory) { return abi.encode( + SESSION_KEY_AUTH_TYPEHASH, skAuth.sessionKey, skAuth.metadataHash // omit authSignature @@ -48,11 +53,13 @@ function toSigningData(SessionKeyAuthorization memory skAuth) pure returns (byte * Where signature is a standard 65-byte EIP-191 or raw ECDSA signature of the packed state. * * Security Model: - * - Off-chain enforcement (Clearnode) should validate session key expiration and usage limits + * - Off-chain enforcement (the Node) should validate session key expiration and usage limits * - On-chain validation only checks cryptographic validity * - Participants are responsible for session key management */ contract SessionKeyValidator is ISignatureValidator { + error ChallengeWithSessionKeyNotSupported(); + /** * @notice Validates a signature using a delegated session key * @dev Validates: @@ -93,4 +100,15 @@ contract SessionKeyValidator is ISignatureValidator { return VALIDATION_FAILURE; } } + + /** + * @notice Challenge signatures via session keys are not supported + */ + function validateChallengeSignature(bytes32, bytes calldata, bytes calldata, address) + external + pure + returns (ValidationResult) + { + revert ChallengeWithSessionKeyNotSupported(); + } } diff --git a/contracts/suggested-contract-design.md b/contracts/suggested-contract-design.md index 652af7f2d..d07b045a3 100644 --- a/contracts/suggested-contract-design.md +++ b/contracts/suggested-contract-design.md @@ -38,6 +38,10 @@ Instead of scattering this logic across many functions, you can centralize it in ## The Proposed Architecture +### Single-node deployment model + +Each `ChannelHub` deployment serves exactly **one node**, identified by the `NODE` immutable set at construction time. The protocol design is node-agnostic (any address can be a node), but this implementation intentionally restricts one hub to one node. Operators who wish to run multiple nodes must deploy a separate `ChannelHub` per node. + ### High-Level Structure The main contract, `ChannelHub.sol`, remains the single source of storage and fund transfers. It contains: diff --git a/contracts/test/ChannelEngine/ChannelEngine_validateTransition.t.sol b/contracts/test/ChannelEngine/ChannelEngine_validateTransition.t.sol new file mode 100644 index 000000000..8294e0a8a --- /dev/null +++ b/contracts/test/ChannelEngine/ChannelEngine_validateTransition.t.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; + +import {ChannelEngine} from "../../src/ChannelEngine.sol"; +import {ChannelStatus, State, StateIntent, Ledger} from "../../src/interfaces/Types.sol"; +import {TestUtils} from "../TestUtils.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelEngineTest_ValidateTransition is Test { + // ======== Helpers ======== + + // Use native ETH (address(0), decimals=18) so validateTokenDecimals passes without a deployed contract. + function _operatingCtx(uint256 lockedFunds) internal view returns (ChannelEngine.TransitionContext memory ctx) { + ctx.status = ChannelStatus.OPERATING; + ctx.prevState = State({ + version: 1, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(0), + decimals: 18, + userAllocation: lockedFunds, + userNetFlow: int256(lockedFunds), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + ctx.lockedFunds = lockedFunds; + ctx.nodeAvailableFunds = 0; + } + + // ======== CLOSE: zero-allocation enforcement ======== + + function test_revert_close_nonZeroUserAllocation() public { + ChannelEngine.TransitionContext memory ctx = _operatingCtx(1000); + + // Same net flows as prevState -> userNfDelta == 0, so no funds would move, but + // userAllocation = 1000 is still positive — previously led to stuck funds. + State memory candidate = TestUtils.nextState( + ctx.prevState, StateIntent.CLOSE, [uint256(1000), uint256(0)], [int256(1000), int256(0)] + ); + + vm.expectRevert(ChannelEngine.IncorrectUserAllocation.selector); + ChannelEngine.validateTransition(ctx, candidate); + } + + function test_revert_close_nonZeroNodeAllocation() public { + ChannelEngine.TransitionContext memory ctx = _operatingCtx(1000); + + // userAllocation = 0 (passes first check), but nodeAllocation = 1000 must fail. + // netFlowsSum = 0 + 1000 = 1000 = allocsSum (passes universal validation) + State memory candidate = TestUtils.nextState( + ctx.prevState, StateIntent.CLOSE, [uint256(0), uint256(1000)], [int256(0), int256(1000)] + ); + + vm.expectRevert(ChannelEngine.IncorrectNodeAllocation.selector); + ChannelEngine.validateTransition(ctx, candidate); + } + + // ======== Token mismatch ======== + + function test_revert_tokenMismatch() public { + address tokenA = address(0xAAAA); + address tokenB = address(0xBBBB); + + ChannelEngine.TransitionContext memory ctx; + ctx.status = ChannelStatus.OPERATING; + ctx.prevState = State({ + version: 1, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: tokenA, + decimals: 18, + userAllocation: 1000, + userNetFlow: int256(1000), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + ctx.lockedFunds = 1000; + ctx.nodeAvailableFunds = 10000; + + // Candidate switches to tokenB while keeping the same amount accounting, + // which would drain tokenB from the hub's balance. + State memory candidate = + TestUtils.nextState(ctx.prevState, StateIntent.WITHDRAW, [uint256(0), uint256(0)], [int256(0), int256(0)]); + candidate.homeLedger.token = tokenB; + + vm.expectRevert(ChannelEngine.TokenMismatch.selector); + ChannelEngine.validateTransition(ctx, candidate); + } +} diff --git a/contracts/test/ChannelHub_Base.t.sol b/contracts/test/ChannelHub_Base.t.sol index a01bd3847..21b00f2e8 100644 --- a/contracts/test/ChannelHub_Base.t.sol +++ b/contracts/test/ChannelHub_Base.t.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 {ChannelStatus, State, StateIntent, Ledger} from "../src/interfaces/Types.sol"; +import {ChannelStatus, State, 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 { @@ -38,12 +39,12 @@ contract ChannelHubTest_Base is Test { uint256 constant INITIAL_BALANCE = 10000; function setUp() public virtual { - // Deploy contracts - cHub = new ChannelHub(ECDSA_SIG_VALIDATOR); - token = new MockERC20("Test Token", "TST", 18); - node = vm.addr(NODE_PK); alice = vm.addr(ALICE_PK); + + // Deploy contracts + cHub = new ChannelHub(ECDSA_SIG_VALIDATOR, node); + token = new MockERC20("Test Token", "TST", 18); aliceSk1 = vm.addr(ALICE_SK1_PK); bob = vm.addr(BOB_PK); @@ -53,14 +54,21 @@ contract ChannelHubTest_Base is Test { vm.startPrank(node); token.approve(address(cHub), INITIAL_BALANCE); - cHub.depositToVault(node, address(token), INITIAL_BALANCE); + cHub.depositToNode(address(token), INITIAL_BALANCE); vm.stopPrank(); + vm.deal(node, INITIAL_BALANCE); + vm.prank(node); + cHub.depositToNode{value: INITIAL_BALANCE}(address(0), INITIAL_BALANCE); + // Register SessionKeyValidator for the node bytes memory skValidatorSig = TestUtils.buildAndSignValidatorRegistration( - vm, SESSION_KEY_VALIDATOR_ID, address(SK_SIG_VALIDATOR), NODE_PK + vm, SESSION_KEY_VALIDATOR_ID, address(SK_SIG_VALIDATOR), NODE_PK, address(cHub) ); - cHub.registerNodeValidator(node, SESSION_KEY_VALIDATOR_ID, SK_SIG_VALIDATOR, skValidatorSig); + cHub.registerNodeValidator(SESSION_KEY_VALIDATOR_ID, SK_SIG_VALIDATOR, skValidatorSig); + + // Advance past VALIDATOR_ACTIVATION_DELAY so the registered validator is usable + vm.warp(block.timestamp + cHub.VALIDATOR_ACTIVATION_DELAY() + 1); vm.prank(alice); token.approve(address(cHub), INITIAL_BALANCE); @@ -69,113 +77,6 @@ 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, - uint64 nonHomeChainId, - address nonHomeChainToken, - 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: "" - }); - } - - function nextState( - State memory state, - StateIntent intent, - uint256[2] memory allocations, - int256[2] memory netFlows, - uint64 nonHomeChainId, - address nonHomeChainToken, - uint8 nonHomeDecimals, - 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: "" - }); - } - function mutualSignStateBothWithEcdsaValidator(State memory state, bytes32 channelId, uint256 userPk) internal pure @@ -197,6 +98,18 @@ contract ChannelHubTest_Base is Test { return state; } + 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, ChannelStatus expectedStatus, @@ -226,7 +139,7 @@ contract ChannelHubTest_Base is Test { ); assertEq(latestState.homeLedger.nodeNetFlow, netFlows[1], string.concat(description, ": Node net flow: ")); - uint256 nodeBalance = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalance = cHub.getNodeBalance(address(token)); 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_Node.t.sol b/contracts/test/ChannelHub_Node.t.sol new file mode 100644 index 000000000..a095c9e0d --- /dev/null +++ b/contracts/test/ChannelHub_Node.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "./ChannelHub_Base.t.sol"; +import {RevertingEthReceiver} from "./mocks/RevertingEthReceiver.sol"; + +import {ChannelHub} from "../src/ChannelHub.sol"; + +contract ChannelHubTest_depositToNode is ChannelHubTest_Base { + // TODO: + + } + +contract ChannelHubTest_withdrawFromNode is ChannelHubTest_Base { + RevertingEthReceiver public revertingReceiver; + address public recipient; + + function setUp() public override { + super.setUp(); + + revertingReceiver = new RevertingEthReceiver(); + recipient = makeAddr("recipient"); + } + + // ========== Input Validation ========== + + function test_reverts_whenCallerIsNotNode() public { + vm.prank(alice); + vm.expectRevert(ChannelHub.IncorrectMsgSender.selector); + cHub.withdrawFromNode(recipient, address(token), DEPOSIT_AMOUNT); + } + + function test_reverts_whenToIsZeroAddress() public { + vm.prank(node); + vm.expectRevert(ChannelHub.InvalidAddress.selector); + cHub.withdrawFromNode(address(0), address(token), DEPOSIT_AMOUNT); + } + + function test_reverts_whenAmountIsZero() public { + vm.prank(node); + vm.expectRevert(ChannelHub.IncorrectAmount.selector); + cHub.withdrawFromNode(recipient, address(token), 0); + } + + function test_reverts_whenInsufficientBalance_ERC20() public { + vm.prank(node); + vm.expectRevert(ChannelHub.InsufficientBalance.selector); + cHub.withdrawFromNode(recipient, address(token), INITIAL_BALANCE + 1); + } + + function test_reverts_whenInsufficientBalance_nativeETH() public { + vm.prank(node); + vm.expectRevert(ChannelHub.InsufficientBalance.selector); + cHub.withdrawFromNode(recipient, address(0), INITIAL_BALANCE + 1); + } + + // ========== Successful Withdrawal — ERC20 ========== + + function test_succeeds_withERC20() public { + vm.prank(node); + cHub.withdrawFromNode(recipient, address(token), DEPOSIT_AMOUNT); + + assertEq(token.balanceOf(recipient), DEPOSIT_AMOUNT, "Recipient should receive tokens"); + assertEq( + cHub.getNodeBalance(address(token)), INITIAL_BALANCE - DEPOSIT_AMOUNT, "Node vault balance should decrease" + ); + } + + function test_emitsEvents_withERC20() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.Withdrawn(address(token), DEPOSIT_AMOUNT); + + vm.expectEmit(true, true, false, true); + emit ChannelHub.NodeBalanceUpdated(address(token), INITIAL_BALANCE - DEPOSIT_AMOUNT); + + vm.prank(node); + cHub.withdrawFromNode(recipient, address(token), DEPOSIT_AMOUNT); + } + + function test_succeeds_fullBalance_ERC20() public { + vm.prank(node); + cHub.withdrawFromNode(recipient, address(token), INITIAL_BALANCE); + + assertEq(token.balanceOf(recipient), INITIAL_BALANCE, "Recipient should receive full balance"); + assertEq(cHub.getNodeBalance(address(token)), 0, "Node vault balance should be zero"); + } + + function test_succeeds_toSelf_ERC20() public { + vm.prank(node); + cHub.withdrawFromNode(node, address(token), DEPOSIT_AMOUNT); + + assertEq(token.balanceOf(node), DEPOSIT_AMOUNT, "Node should receive tokens"); + assertEq( + cHub.getNodeBalance(address(token)), INITIAL_BALANCE - DEPOSIT_AMOUNT, "Node vault balance should decrease" + ); + } + + // ========== Successful Withdrawal — Native ETH ========== + + function test_succeeds_withNativeETH() public { + vm.prank(node); + cHub.withdrawFromNode(recipient, address(0), DEPOSIT_AMOUNT); + + assertEq(recipient.balance, DEPOSIT_AMOUNT, "Recipient should receive ETH"); + assertEq( + cHub.getNodeBalance(address(0)), INITIAL_BALANCE - DEPOSIT_AMOUNT, "Node vault balance should decrease" + ); + } + + function test_emitsEvents_withNativeETH() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.Withdrawn(address(0), DEPOSIT_AMOUNT); + + vm.expectEmit(true, true, false, true); + emit ChannelHub.NodeBalanceUpdated(address(0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + + vm.prank(node); + cHub.withdrawFromNode(recipient, address(0), DEPOSIT_AMOUNT); + } + + function test_succeeds_fullBalance_nativeETH() public { + vm.prank(node); + cHub.withdrawFromNode(recipient, address(0), INITIAL_BALANCE); + + assertEq(recipient.balance, INITIAL_BALANCE, "Recipient should receive full ETH balance"); + assertEq(cHub.getNodeBalance(address(0)), 0, "Node vault balance should be zero"); + } + + function test_succeedss_toSelf_nativeETH() public { + vm.prank(node); + cHub.withdrawFromNode(node, address(0), DEPOSIT_AMOUNT); + + assertEq(node.balance, DEPOSIT_AMOUNT, "Node should receive ETH"); + assertEq( + cHub.getNodeBalance(address(0)), INITIAL_BALANCE - DEPOSIT_AMOUNT, "Node vault balance should decrease" + ); + } + + // ========== Atomicity: Revert on Transfer Failure ========== + + function test_reverts_whenERC20TransferFails_balanceUnchanged() public { + token.setFailTransfers(true); + + uint256 balanceBefore = cHub.getNodeBalance(address(token)); + + vm.prank(node); + vm.expectRevert(); + cHub.withdrawFromNode(recipient, address(token), DEPOSIT_AMOUNT); + + assertEq(cHub.getNodeBalance(address(token)), balanceBefore, "Vault balance must be unchanged on revert"); + assertEq(cHub.getReclaimBalance(recipient, address(token)), 0, "No reclaim should be created"); + } + + function test_reverts_whenNativeETHTransferFails_balanceUnchanged() public { + uint256 balanceBefore = cHub.getNodeBalance(address(0)); + + vm.prank(node); + vm.expectRevert( + abi.encodeWithSelector(ChannelHub.NativeTransferFailed.selector, address(revertingReceiver), DEPOSIT_AMOUNT) + ); + cHub.withdrawFromNode(address(revertingReceiver), address(0), DEPOSIT_AMOUNT); + + assertEq(cHub.getNodeBalance(address(0)), balanceBefore, "Vault balance must be unchanged on revert"); + assertEq(cHub.getReclaimBalance(address(revertingReceiver), address(0)), 0, "No reclaim should be created"); + } +} diff --git a/contracts/test/ChannelHub_allFlowsInvokePurge.t.sol b/contracts/test/ChannelHub_allFlowsInvokePurge.t.sol new file mode 100644 index 000000000..e907a72f6 --- /dev/null +++ b/contracts/test/ChannelHub_allFlowsInvokePurge.t.sol @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "./ChannelHub_Base.t.sol"; +import {TestUtils, SESSION_KEY_VALIDATOR_ID} from "./TestUtils.sol"; +import {TestChannelHub} from "./TestChannelHub.sol"; + +import {Utils} from "../src/Utils.sol"; +import { + ChannelDefinition, + State, + StateIntent, + Ledger, + ParticipantIndex, + EscrowStatus +} from "../src/interfaces/Types.sol"; +import {EscrowDepositEngine} from "../src/EscrowDepositEngine.sol"; +import {EscrowWithdrawalEngine} from "../src/EscrowWithdrawalEngine.sol"; + +// forge-lint: disable-start(unsafe-typecast) + +/** + * Black-box tests verifying that _purgeEscrowDeposits is invoked on every protocol + * operation that is expected to call it, and is NOT called by operations that should + * not touch the purge queue. + * + * Detection mechanism: a FINALIZED sentinel entry is injected into the deposit queue + * immediately before the tested action via _snapshotAndInjectSentinel(). Because + * FINALIZED entries are skippable, any purge call advances escrowHead past the + * sentinel. _assertPurgeInvoked() confirms escrowHead moved relative to the snapshot. + * + * Invokes purge (_purgeEscrowDeposits is called): + * === home chain flows === + * - createChannel + * - depositToChannel + * - withdrawFromChannel + * - checkpointChannel + * - closeChannel + * - challengeChannel + * - initiateEscrowDeposit + * - finalizeEscrowDeposit + * - initiateEscrowWithdrawal + * - finalizeEscrowWithdrawal + * + * === non-home-chain escrow flows === + * - initiateEscrowDeposit + * - challengeEscrowDeposit + * - finalizeEscrowDeposit (cooperative) + * - finalizeEscrowDeposit (unilateral timeout) + * - initiateEscrowWithdrawal + * - challengeEscrowWithdrawal + * - finalizeEscrowWithdrawal (cooperative) + * - finalizeEscrowWithdrawal (unilateral timeout) + * + * === migration flows === + * - initiateMigration (home chain OUT) + * - initiateMigration (non-home chain IN) + * - finalizeMigration (new home chain IN) + * - finalizeMigration (old home chain OUT) + * + * - purgeEscrowDeposits (public) + * + * Does NOT invoke purge: + * - depositToNode + * - withdrawFromNode + */ +contract ChannelHubTest_allFlowsInvokePurge is ChannelHubTest_Base { + TestChannelHub internal tHub; + + ChannelDefinition internal def; + bytes32 internal channelId; + + // Separate channel for non-home-chain escrow flows + ChannelDefinition internal bobDef; + bytes32 internal bobChannelId; + + uint64 constant FOREIGN_CHAIN_ID = 42; + address constant FOREIGN_TOKEN = address(42); + uint256 constant WITHDRAWAL_AMOUNT = 300; + + bytes32 constant SENTINEL_ESCROW_ID = keccak256("purge_sentinel_escrow_id"); + uint256 internal _headBeforeAction; + + // ======== setUp ======== + + function setUp() public override { + super.setUp(); + + // Deploy TestChannelHub (a ChannelHub subtype) and assign it to the base's + // cHub field so all inherited helpers keep working. + tHub = new TestChannelHub(ECDSA_SIG_VALIDATOR, node); + cHub = tHub; + + // super.setUp() already spent node's INITIAL_BALANCE on the old cHub vault. + // Re-mint so node can fund the new tHub vault. + token.mint(node, INITIAL_BALANCE); + vm.startPrank(node); + token.approve(address(cHub), INITIAL_BALANCE); + cHub.depositToNode(address(token), INITIAL_BALANCE); + vm.stopPrank(); + + bytes memory skValidatorSig = TestUtils.buildAndSignValidatorRegistration( + vm, SESSION_KEY_VALIDATOR_ID, address(SK_SIG_VALIDATOR), NODE_PK, address(cHub) + ); + cHub.registerNodeValidator(SESSION_KEY_VALIDATOR_ID, SK_SIG_VALIDATOR, skValidatorSig); + + vm.prank(alice); + token.approve(address(cHub), INITIAL_BALANCE); + + vm.prank(bob); + token.approve(address(cHub), INITIAL_BALANCE); + + 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); + } + + // ======== Purge detection helpers ======== + + /// @dev Snapshots the current escrowHead and appends a FINALIZED sentinel to the + /// deposit queue at that position. Any subsequent purge call will skip the + /// sentinel and advance escrowHead, making the call detectable. + function _snapshotAndInjectSentinel() internal { + _headBeforeAction = tHub.escrowHead(); + tHub.workaround_setEscrowDeposit( + SENTINEL_ESCROW_ID, bytes32(0), EscrowStatus.FINALIZED, address(0), address(0), 0, 0, 0, address(0) + ); + tHub.workaround_addEscrowDepositId(SENTINEL_ESCROW_ID); + } + + function _assertPurgeInvoked() internal view { + assertGt(tHub.escrowHead(), _headBeforeAction, "_purgeEscrowDeposits should have been called"); + } + + function _assertPurgeNotInvoked() internal view { + assertEq(tHub.escrowHead(), _headBeforeAction, "_purgeEscrowDeposits should NOT have been called"); + } + + // ======== Protocol helpers ======== + + 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + vm.prank(alice); + cHub.createChannel(def, state); + } + + /// @dev Submits INITIATE_ESCROW_DEPOSIT on the home chain (uses alice's channel). + /// The node locks DEPOSIT_AMOUNT from its vault to back the cross-chain deposit. + function _initiateEscrowDepositHomeChain() internal returns (bytes32 escrowId, State memory initState) { + State memory prevState = _createSimpleChannel(); + initState = TestUtils.nextState( + prevState, + StateIntent.INITIATE_ESCROW_DEPOSIT, + [DEPOSIT_AMOUNT, DEPOSIT_AMOUNT], + [int256(DEPOSIT_AMOUNT), int256(DEPOSIT_AMOUNT)], + FOREIGN_CHAIN_ID, + FOREIGN_TOKEN, + [DEPOSIT_AMOUNT, uint256(0)], + [int256(DEPOSIT_AMOUNT), int256(0)] + ); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + escrowId = Utils.getEscrowId(channelId, initState.version); + vm.prank(node); + cHub.initiateEscrowDeposit(def, initState); + } + + /// @dev Submits INITIATE_ESCROW_WITHDRAWAL on the home chain (uses alice's channel). + /// No funds move yet; WITHDRAWAL_AMOUNT will be paid on the foreign chain. + function _initiateEscrowWithdrawalHomeChain() internal returns (bytes32 escrowId, State memory initState) { + State memory prevState = _createSimpleChannel(); + initState = TestUtils.nextState( + prevState, + StateIntent.INITIATE_ESCROW_WITHDRAWAL, + [DEPOSIT_AMOUNT, uint256(0)], + [int256(DEPOSIT_AMOUNT), int256(0)], + FOREIGN_CHAIN_ID, + FOREIGN_TOKEN, + [uint256(0), WITHDRAWAL_AMOUNT], + [int256(0), int256(WITHDRAWAL_AMOUNT)] + ); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + escrowId = Utils.getEscrowId(channelId, initState.version); + vm.prank(alice); + cHub.initiateEscrowWithdrawal(def, initState); + } + + /// @dev Submits INITIATE_ESCROW_DEPOSIT on the non-home chain (uses bob's channel). + /// Returns the escrowId and the signed initState for use in subsequent steps. + 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 Submits INITIATE_ESCROW_WITHDRAWAL on the non-home chain (uses bob's channel). + /// The node locks DEPOSIT_AMOUNT from its vault on the home chain. + 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); + } + + /// @dev Initiates migration OUT on the home chain (alice's channel must already be OPERATING). + function _initiateMigrationHomeChain(State memory prevState) internal returns (State memory initState) { + initState = TestUtils.nextState( + prevState, + StateIntent.INITIATE_MIGRATION, + [DEPOSIT_AMOUNT, uint256(0)], + [int256(DEPOSIT_AMOUNT), int256(0)], + FOREIGN_CHAIN_ID, + FOREIGN_TOKEN, + [uint256(0), DEPOSIT_AMOUNT], + [int256(0), int256(DEPOSIT_AMOUNT)] + ); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + vm.prank(alice); + cHub.initiateMigration(def, initState); + } + + /// @dev Initiates migration IN on the non-home chain (bob's channel, VOID → MIGRATING_IN). + /// State is passed with homeLedger=FOREIGN, nonHomeLedger=LOCAL; + /// the contract swaps them before storing, so the returned state reflects what is stored + /// (homeLedger=LOCAL, nonHomeLedger=FOREIGN) — ready for use as prevState in nextState(). + function _initiateMigrationNonHomeChain() internal returns (State memory storedState) { + State memory initState = State({ + version: 1, + intent: StateIntent.INITIATE_MIGRATION, + 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); + vm.prank(bob); + cHub.initiateMigration(bobDef, initState); + + // Mirror the contract's ledger swap so the caller has the stored representation. + storedState = initState; + storedState.homeLedger = initState.nonHomeLedger; + storedState.nonHomeLedger = initState.homeLedger; + storedState.userSig = ""; + storedState.nodeSig = ""; + } + + // ======== Tests: home chain flows ======== + + function test_purgeInvoked_onCreateChannel() public { + _snapshotAndInjectSentinel(); + _createSimpleChannel(); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onDepositToChannel() public { + State memory prevState = _createSimpleChannel(); + _snapshotAndInjectSentinel(); + + State memory candidate = TestUtils.nextState( + prevState, + StateIntent.DEPOSIT, + [DEPOSIT_AMOUNT * 2, DEPOSIT_AMOUNT], + [int256(DEPOSIT_AMOUNT) * 2, int256(DEPOSIT_AMOUNT)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + vm.prank(alice); + cHub.depositToChannel(channelId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onWithdrawFromChannel() public { + State memory prevState = _createSimpleChannel(); + _snapshotAndInjectSentinel(); + + State memory candidate = TestUtils.nextState( + prevState, + StateIntent.WITHDRAW, + [DEPOSIT_AMOUNT - 500, uint256(0)], + [int256(DEPOSIT_AMOUNT) - 500, int256(0)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + vm.prank(alice); + cHub.withdrawFromChannel(channelId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onCheckpointChannel() public { + State memory prevState = _createSimpleChannel(); + _snapshotAndInjectSentinel(); + + State memory candidate = TestUtils.nextState( + prevState, StateIntent.OPERATE, [DEPOSIT_AMOUNT - 500, uint256(0)], [int256(DEPOSIT_AMOUNT), -500] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + vm.prank(alice); + cHub.checkpointChannel(channelId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onCloseChannel() public { + State memory prevState = _createSimpleChannel(); + _snapshotAndInjectSentinel(); + + State memory candidate = + TestUtils.nextState(prevState, StateIntent.CLOSE, [uint256(0), uint256(0)], [int256(0), int256(0)]); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + vm.prank(alice); + cHub.closeChannel(channelId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onChallengeChannel() public { + State memory prevState = _createSimpleChannel(); + _snapshotAndInjectSentinel(); + + State memory candidate = TestUtils.nextState( + prevState, StateIntent.OPERATE, [DEPOSIT_AMOUNT - 500, uint256(0)], [int256(DEPOSIT_AMOUNT), -500] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, candidate, NODE_PK); + vm.prank(node); + cHub.challengeChannel(channelId, candidate, challengerSig, ParticipantIndex.NODE); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onInitiateEscrowDeposit_homeChain() public { + _snapshotAndInjectSentinel(); + _initiateEscrowDepositHomeChain(); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeEscrowDeposit_homeChain() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowDepositHomeChain(); + _snapshotAndInjectSentinel(); + + // User allocation increases by DEPOSIT_AMOUNT; node allocation released. + State memory candidate = TestUtils.nextState( + initState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [DEPOSIT_AMOUNT * 2, uint256(0)], + [int256(DEPOSIT_AMOUNT), int256(DEPOSIT_AMOUNT)], + FOREIGN_CHAIN_ID, + FOREIGN_TOKEN, + [uint256(0), uint256(0)], + [int256(DEPOSIT_AMOUNT), -int256(DEPOSIT_AMOUNT)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + vm.prank(alice); + cHub.finalizeEscrowDeposit(channelId, escrowId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onInitiateEscrowWithdrawal_homeChain() public { + _snapshotAndInjectSentinel(); + _initiateEscrowWithdrawalHomeChain(); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeEscrowWithdrawal_homeChain() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowWithdrawalHomeChain(); + _snapshotAndInjectSentinel(); + + // User allocation decreases by WITHDRAWAL_AMOUNT; node balance released from channel. + // nonHomeLedger.chainId must differ from block.chainid (home chain path via ChannelEngine). + State memory candidate = TestUtils.nextState( + initState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [DEPOSIT_AMOUNT - WITHDRAWAL_AMOUNT, uint256(0)], + [int256(DEPOSIT_AMOUNT), -int256(WITHDRAWAL_AMOUNT)], + FOREIGN_CHAIN_ID, + FOREIGN_TOKEN, + [uint256(0), uint256(0)], + [-int256(WITHDRAWAL_AMOUNT), int256(WITHDRAWAL_AMOUNT)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + vm.prank(alice); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, candidate); + + _assertPurgeInvoked(); + } + + // ======== Tests: non-home-chain escrow flows ======== + + function test_purgeInvoked_onInitiateEscrowDeposit() public { + _snapshotAndInjectSentinel(); + _initiateEscrowDeposit(); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onChallengeEscrowDeposit() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowDeposit(); + _snapshotAndInjectSentinel(); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + vm.prank(bob); + cHub.challengeEscrowDeposit(escrowId, sig, ParticipantIndex.USER); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeEscrowDeposit_cooperative() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowDeposit(); + _snapshotAndInjectSentinel(); + + State memory candidate = TestUtils.nextState( + initState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [DEPOSIT_AMOUNT, uint256(0)], + [int256(0), int256(DEPOSIT_AMOUNT)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [int256(DEPOSIT_AMOUNT), -int256(DEPOSIT_AMOUNT)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, bobChannelId, BOB_PK); + vm.prank(node); + cHub.finalizeEscrowDeposit(bobChannelId, escrowId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeEscrowDeposit_unilateral() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowDeposit(); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + vm.prank(bob); + cHub.challengeEscrowDeposit(escrowId, sig, ParticipantIndex.USER); + + vm.warp(block.timestamp + EscrowDepositEngine.CHALLENGE_DURATION + 1); + _snapshotAndInjectSentinel(); + + vm.prank(node); + cHub.finalizeEscrowDeposit(bobChannelId, escrowId, initState); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onInitiateEscrowWithdrawal() public { + _snapshotAndInjectSentinel(); + _initiateEscrowWithdrawal(); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onChallengeEscrowWithdrawal() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowWithdrawal(); + _snapshotAndInjectSentinel(); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + vm.prank(bob); + cHub.challengeEscrowWithdrawal(escrowId, sig, ParticipantIndex.USER); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeEscrowWithdrawal_cooperative() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowWithdrawal(); + _snapshotAndInjectSentinel(); + + // User allocation drops to 0 (full amount withdrawn); node NF decreases by DEPOSIT_AMOUNT. + // nonHomeLedger.chainId == block.chainid (non-home chain path via EscrowWithdrawalEngine). + State memory candidate = TestUtils.nextState( + initState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [uint256(0), uint256(0)], + [int256(DEPOSIT_AMOUNT), -int256(DEPOSIT_AMOUNT)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [-int256(DEPOSIT_AMOUNT), int256(DEPOSIT_AMOUNT)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, bobChannelId, BOB_PK); + vm.prank(node); + cHub.finalizeEscrowWithdrawal(bobChannelId, escrowId, candidate); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeEscrowWithdrawal_unilateral() public { + (bytes32 escrowId, State memory initState) = _initiateEscrowWithdrawal(); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + vm.prank(bob); + cHub.challengeEscrowWithdrawal(escrowId, sig, ParticipantIndex.USER); + + vm.warp(block.timestamp + EscrowWithdrawalEngine.CHALLENGE_DURATION + 1); + _snapshotAndInjectSentinel(); + + vm.prank(node); + cHub.finalizeEscrowWithdrawal(bobChannelId, escrowId, initState); + + _assertPurgeInvoked(); + } + + // ======== Tests: migration flows ======== + + function test_purgeInvoked_onInitiateMigration_homeChain() public { + State memory prevState = _createSimpleChannel(); + _snapshotAndInjectSentinel(); + _initiateMigrationHomeChain(prevState); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onInitiateMigration_nonHomeChain() public { + _snapshotAndInjectSentinel(); + _initiateMigrationNonHomeChain(); + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeMigration_newHomeChain() public { + // storedInitState has homeLedger=LOCAL (block.chainid), nonHomeLedger=FOREIGN + State memory storedInitState = _initiateMigrationNonHomeChain(); + _snapshotAndInjectSentinel(); + + // userMigratedAlloc = storedInitState.nonHomeLedger.userAlloc = DEPOSIT_AMOUNT + // userNfDelta = 0 (userNF must equal storedInitState.homeLedger.userNF = 0) + // nodeNfDelta = 0 (nodeNF must equal storedInitState.homeLedger.nodeNF = DEPOSIT_AMOUNT) + State memory finalizeState = TestUtils.nextState( + storedInitState, + StateIntent.FINALIZE_MIGRATION, + [DEPOSIT_AMOUNT, uint256(0)], + [int256(0), int256(DEPOSIT_AMOUNT)], + FOREIGN_CHAIN_ID, + FOREIGN_TOKEN, + [uint256(0), uint256(0)], + [int256(0), int256(0)] + ); + finalizeState = mutualSignStateBothWithEcdsaValidator(finalizeState, bobChannelId, BOB_PK); + vm.prank(bob); + cHub.finalizeMigration(bobChannelId, finalizeState); + + _assertPurgeInvoked(); + } + + function test_purgeInvoked_onFinalizeMigration_oldHomeChain() public { + State memory prevState = _createSimpleChannel(); + State memory initState = _initiateMigrationHomeChain(prevState); + _snapshotAndInjectSentinel(); + + // Candidate has homeLedger=FOREIGN (new home), nonHomeLedger=LOCAL (block.chainid). + // Contract detects nonHomeLedger.chainId == block.chainid → swaps before validation. + // Swap initState ledgers to get a base whose homeLedger.chainId == FOREIGN_CHAIN_ID, + // so that nextState preserves the correct chainId/token for the finalize candidate. + State memory swappedBase = initState; + swappedBase.homeLedger = initState.nonHomeLedger; + swappedBase.nonHomeLedger = initState.homeLedger; + + State memory finalizeState = TestUtils.nextState( + swappedBase, + StateIntent.FINALIZE_MIGRATION, + [DEPOSIT_AMOUNT, uint256(0)], + [int256(DEPOSIT_AMOUNT), int256(0)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [int256(0), int256(0)] + ); + finalizeState = mutualSignStateBothWithEcdsaValidator(finalizeState, channelId, ALICE_PK); + vm.prank(alice); + cHub.finalizeMigration(channelId, finalizeState); + + _assertPurgeInvoked(); + } + + // ======== Tests: public purge ======== + + function test_purgeInvoked_onPurgeEscrowDeposits() public { + _snapshotAndInjectSentinel(); + cHub.purgeEscrowDeposits(1); + _assertPurgeInvoked(); + } + + // ======== Tests: does NOT invoke purge ======== + + function test_purgeNotInvoked_onDepositToNode() public { + _snapshotAndInjectSentinel(); + + token.mint(node, DEPOSIT_AMOUNT); + vm.startPrank(node); + token.approve(address(cHub), DEPOSIT_AMOUNT); + cHub.depositToNode(address(token), DEPOSIT_AMOUNT); + vm.stopPrank(); + + _assertPurgeNotInvoked(); + } + + function test_purgeNotInvoked_onWithdrawFromNode() public { + _snapshotAndInjectSentinel(); + + vm.prank(node); + cHub.withdrawFromNode(node, address(token), DEPOSIT_AMOUNT); + + _assertPurgeNotInvoked(); + } +} + +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_Challenge_Base.t.sol b/contracts/test/ChannelHub_challenge/ChannelHub_Challenge_Base.t.sol similarity index 68% rename from contracts/test/ChannelHub_Challenge_Base.t.sol rename to contracts/test/ChannelHub_challenge/ChannelHub_Challenge_Base.t.sol index 92bccdf39..037a9d3ca 100644 --- a/contracts/test/ChannelHub_Challenge_Base.t.sol +++ b/contracts/test/ChannelHub_challenge/ChannelHub_Challenge_Base.t.sol @@ -1,11 +1,10 @@ // 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, DEFAULT_SIG_VALIDATOR_ID} from "../src/interfaces/Types.sol"; -import {TestUtils} from "./TestUtils.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. @@ -64,16 +63,4 @@ abstract contract ChannelHubTest_Challenge_Base is ChannelHubTest_Base { vm.prank(alice); cHub.createChannel(def, initState); } - - 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); - } } diff --git a/contracts/test/ChannelHub_challengeHomeChain.t.sol b/contracts/test/ChannelHub_challenge/ChannelHub_challengeHomeChain.t.sol similarity index 81% rename from contracts/test/ChannelHub_challengeHomeChain.t.sol rename to contracts/test/ChannelHub_challenge/ChannelHub_challengeHomeChain.t.sol index 6ce1b31ac..505857737 100644 --- a/contracts/test/ChannelHub_challengeHomeChain.t.sol +++ b/contracts/test/ChannelHub_challenge/ChannelHub_challengeHomeChain.t.sol @@ -3,18 +3,20 @@ pragma solidity 0.8.30; import {ChannelHubTest_Challenge_Base} from "./ChannelHub_Challenge_Base.t.sol"; -import {Utils} from "../src/Utils.sol"; +// forge-lint: disable-start(unsafe-typecast) + +import {Utils} from "../../src/Utils.sol"; +import {TestUtils} from "../TestUtils.sol"; import { State, ChannelDefinition, StateIntent, Ledger, ChannelStatus, - ParticipantIndex, - DEFAULT_SIG_VALIDATOR_ID -} from "../src/interfaces/Types.sol"; -import {ChannelHub} from "../src/ChannelHub.sol"; -import {ChannelEngine} from "../src/ChannelEngine.sol"; + 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 @@ -28,11 +30,13 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch 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 + - a channel can NOT be challenged with CLOSE intent (must use closeChannel function for that) - 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 { @@ -42,13 +46,14 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch 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)]); + State memory stateV1 = TestUtils.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)]); + TestUtils.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 @@ -74,8 +79,9 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch function test_challengeWithExistingState_notEnforcedAgain() public { // Checkpoint a new state - State memory stateV1 = - nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + State memory stateV1 = TestUtils.nextState( + initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)] + ); stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); vm.prank(alice); @@ -106,9 +112,22 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch ); } + function test_revert_challengeWithCloseIntent() public { + State memory closeState = + TestUtils.nextState(initState, StateIntent.CLOSE, [uint256(0), uint256(0)], [int256(0), int256(0)]); + closeState = mutualSignStateBothWithEcdsaValidator(closeState, channelId, ALICE_PK); + + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, closeState, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.challengeChannel(channelId, closeState, challengerSig, ParticipantIndex.NODE); + } + function test_challengeFinalization_afterTimeout() public { - State memory stateV1 = - nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + State memory stateV1 = TestUtils.nextState( + initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)] + ); stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); // Challenge with current state @@ -120,7 +139,7 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch vm.warp(block.timestamp + CHALLENGE_DURATION + 1); uint256 aliceBalanceBefore = token.balanceOf(alice); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceBefore = cHub.getNodeBalance(address(token)); // Finalize challenge by closing the channel (unilateral closure) // When doing unilateral closure after timeout, any state works @@ -133,7 +152,7 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch ); uint256 aliceBalanceAfter = token.balanceOf(alice); - uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceAfter = cHub.getNodeBalance(address(token)); assertEq(aliceBalanceAfter, aliceBalanceBefore + 900, "Alice should receive her allocation"); // Node balance should remain unchanged because: @@ -148,8 +167,9 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch 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)]); + State memory stateV1 = TestUtils.nextState( + initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)] + ); stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); // Challenge with stateV1 @@ -168,7 +188,7 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch // 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)]); + TestUtils.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) @@ -192,13 +212,14 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch 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)]); + State memory stateV1 = TestUtils.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)]); + TestUtils.nextState(stateV1, StateIntent.OPERATE, [uint256(950), uint256(0)], [int256(1000), int256(-50)]); stateV2 = mutualSignStateBothWithEcdsaValidator(stateV2, channelId, ALICE_PK); // Challenge with stateV2 @@ -223,8 +244,9 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch function test_revert_resolveChallenge_withNewerState_afterTimeout() public { // State V1 - State memory stateV1 = - nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + State memory stateV1 = TestUtils.nextState( + initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)] + ); stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); // Challenge @@ -237,7 +259,7 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch // 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)]); + TestUtils.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 @@ -263,8 +285,9 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch ); // Try to challenge again (should fail) - State memory stateV1 = - nextState(initState, StateIntent.OPERATE, [uint256(850), uint256(0)], [int256(1000), int256(-150)]); + State memory stateV1 = TestUtils.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); @@ -276,8 +299,9 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch function test_revert_challengeWithOlderState() public { // State V1 - State memory stateV1 = - nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + State memory stateV1 = TestUtils.nextState( + initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)] + ); stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); // Checkpoint V1 @@ -291,6 +315,30 @@ contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Ch 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 = TestUtils.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 { @@ -314,7 +362,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal super.setUp(); createChannelWithDeposit(); - initiateEscrowDepositState = nextState( + initiateEscrowDepositState = TestUtils.nextState( initState, StateIntent.INITIATE_ESCROW_DEPOSIT, [uint256(1000), uint256(500)], @@ -329,7 +377,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal escrowId = Utils.getEscrowId(channelId, initiateEscrowDepositVersion); - finalizeEscrowDepositState = nextState( + finalizeEscrowDepositState = TestUtils.nextState( initiateEscrowDepositState, StateIntent.FINALIZE_ESCROW_DEPOSIT, [uint256(1500), uint256(0)], @@ -367,7 +415,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal } function test_challenge_initiateEscrowDeposit_asExisting() public { - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); // Challenge with already enforced initiateEscrowDepositState state @@ -397,7 +445,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); // Resolve challenge with newer initiateEscrowDepositState state (before timeout) - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); // Verify challenge was resolved @@ -414,7 +462,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal function test_challenge_finalizeEscrowDeposit_asNew() public { // First enforce INITIATE_ESCROW_DEPOSIT on-chain (required for FINALIZE to be valid) - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); // Now challenge with FINALIZE_ESCROW_DEPOSIT @@ -442,7 +490,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal function test_challenge_finalizeEscrowDeposit_asExisting() public { // First enforce INITIATE_ESCROW_DEPOSIT on-chain - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); // Then enforce FINALIZE_ESCROW_DEPOSIT on-chain @@ -471,7 +519,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal function test_challenge_finalizeEscrowDeposit_resolve() public { // First enforce INITIATE_ESCROW_DEPOSIT on-chain - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); // Challenge with older initiate state @@ -523,7 +571,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal function test_revert_onChallengeEscrowDeposit() public { // First enforce INITIATE_ESCROW_DEPOSIT on-chain - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); // Challenge with INITIATE_ESCROW_DEPOSIT state @@ -531,7 +579,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Chal signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); vm.prank(node); - vm.expectRevert(ChannelHub.NoChannelIdFound.selector); + vm.expectRevert(ChannelHub.NoChannelIdFoundForEscrow.selector); cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.NODE); } } @@ -557,7 +605,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowWithdrawal is ChannelHubTest_C super.setUp(); createChannelWithDeposit(); - initiateEscrowWithdrawalState = nextState( + initiateEscrowWithdrawalState = TestUtils.nextState( initState, StateIntent.INITIATE_ESCROW_WITHDRAWAL, [uint256(1000), uint256(0)], @@ -572,7 +620,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowWithdrawal is ChannelHubTest_C escrowId = Utils.getEscrowId(channelId, initiateEscrowWithdrawalVersion); - finalizeEscrowWithdrawalState = nextState( + finalizeEscrowWithdrawalState = TestUtils.nextState( initiateEscrowWithdrawalState, StateIntent.FINALIZE_ESCROW_WITHDRAWAL, [uint256(700), uint256(0)], @@ -767,7 +815,7 @@ contract ChannelHubTest_Challenge_HomeChain_EscrowWithdrawal is ChannelHubTest_C signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); vm.prank(node); - vm.expectRevert(ChannelHub.NoChannelIdFound.selector); + vm.expectRevert(ChannelHub.NoChannelIdFoundForEscrow.selector); cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.NODE); } } @@ -779,10 +827,8 @@ contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Chal - 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 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 - a channel can NOT be challenged when in MIGRATED_OUT status - - a channel can NOT be challenged in Operating status with finalize migration state + - a channel can NOT be challenged with FINALIZE_MIGRATION intent on home chain (must use finalizeMigration function for that) */ uint64 initiateMigrationVersion = 1; @@ -804,7 +850,7 @@ contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Chal createChannelWithDeposit(); // INITIATE_MIGRATION state: - initiateMigrationState = nextState( + initiateMigrationState = TestUtils.nextState( initState, StateIntent.INITIATE_MIGRATION, [uint256(700), uint256(0)], @@ -817,7 +863,7 @@ contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Chal initiateMigrationState = mutualSignStateBothWithEcdsaValidator(initiateMigrationState, channelId, ALICE_PK); // FINALIZE_MIGRATION state: Allocations zero out on old home, user receives allocation on new home - finalizeMigrationState = nextState( + finalizeMigrationState = TestUtils.nextState( initiateMigrationState, StateIntent.FINALIZE_MIGRATION, [uint256(0), uint256(0)], // Old home: allocations zero out @@ -834,84 +880,11 @@ contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Chal finalizeMigrationState = mutualSignStateBothWithEcdsaValidator(finalizeMigrationState, channelId, ALICE_PK); // OPERATE state after migration initiation (for resolving challenge) - operateAfterMigrationInitState = nextState( + operateAfterMigrationInitState = TestUtils.nextState( initiateMigrationState, StateIntent.OPERATE, [uint256(650), uint256(0)], [int256(1000), int256(-350)] ); operateAfterMigrationInitState = mutualSignStateBothWithEcdsaValidator(operateAfterMigrationInitState, channelId, ALICE_PK); - - // Setup for NEW home chain tests (migration IN) - // Create a new channel definition with different nonce - newHomeDef = ChannelDefinition({ - challengeDuration: CHALLENGE_DURATION, - user: alice, - node: node, - nonce: uint64(42), // Different nonce to create a new channel - approvedSignatureValidators: DEFAULT_SIG_VALIDATOR_ID, - metadata: bytes32(0) - }); - newHomeChannelId = Utils.getChannelId(newHomeDef, cHub.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_initiateMigration_fromOperating() public { @@ -997,74 +970,6 @@ contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Chal ); } - 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" - ); - } - function test_revert_challenge_migratedOut() public { // First initiate migration vm.prank(alice); @@ -1087,15 +992,12 @@ contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Chal 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) + function test_revert_challengeWithFinalizeMigrationIntent() public { 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); + vm.expectRevert(ChannelHub.IncorrectStateIntent.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..030d5e789 --- /dev/null +++ b/contracts/test/ChannelHub_challenge/ChannelHub_challengeNonHomeChain.t.sol @@ -0,0 +1,620 @@ +// 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 {TestUtils} from "../TestUtils.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 + - challenged escrow deposit unilateral finalization emits event with INITIATE state as candidate, ignoring arbitrary candidate input + - 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 = TestUtils.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.getNodeBalance(address(token)); + + // 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.getNodeBalance(address(token)), + 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_challengedEscrowDeposit_unilateralFinalize_emitsInitState_ignoringArbitraryCandidate() public { + _challengeEscrowDeposit(); + + vm.warp(block.timestamp + EscrowDepositEngine.CHALLENGE_DURATION + 1); + + State memory poisonedCandidate = initiateEscrowDepositState; + poisonedCandidate.version = 999999; + + vm.expectEmit(true, true, false, true); + emit ChannelHub.EscrowDepositFinalized(escrowId, channelId, initiateEscrowDepositState); + + vm.prank(node); + cHub.finalizeEscrowDeposit(channelId, escrowId, poisonedCandidate); + } + + 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 + - challenged escrow withdrawal unilateral finalization emits event with INITIATE state as candidate, ignoring arbitrary candidate input + - 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 = TestUtils.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.getNodeBalance(address(token)); + + // 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)); + + // 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.getNodeBalance(address(token)), + 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_challengedEscrowWithdrawal_unilateralFinalize_emitsInitState_ignoringArbitraryCandidate() public { + _challengeEscrowWithdrawal(); + + vm.warp(block.timestamp + EscrowWithdrawalEngine.CHALLENGE_DURATION + 1); + + State memory poisonedCandidate = initiateEscrowWithdrawalState; + poisonedCandidate.version = 999999; + + vm.expectEmit(true, true, false, true); + emit ChannelHub.EscrowWithdrawalFinalized(escrowId, channelId, initiateEscrowWithdrawalState); + + vm.prank(node); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, poisonedCandidate); + } + + 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 + - a channel in Migrating_in status can be challenged with FINALIZE_MIGRATION intent (with version+1) + */ + + // New channel for testing NEW home chain behavior + ChannelDefinition newHomeDef; + bytes32 newHomeChannelId; + + uint64 initiateMigrationVersion = 1; + State initiateMigrationState; + uint64 finalizeMigrationVersion = 2; + State finalizeMigrationState; + 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) + initiateMigrationState = 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: "" + }); + initiateMigrationState = + mutualSignStateBothWithEcdsaValidator(initiateMigrationState, newHomeChannelId, ALICE_PK); + + finalizeMigrationState = State({ + version: finalizeMigrationVersion, + intent: StateIntent.FINALIZE_MIGRATION, + metadata: bytes32(0), + nonHomeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: 0, + userNetFlow: 500, + nodeAllocation: 0, + nodeNetFlow: -500 + }), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 500, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 500 + }), + userSig: "", + nodeSig: "" + }); + finalizeMigrationState = + mutualSignStateBothWithEcdsaValidator(finalizeMigrationState, 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, initiateMigrationState); + + // 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, initiateMigrationState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(newHomeChannelId, initiateMigrationState, 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, initiateMigrationState); + + // 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" + ); + } + + function test_challenge_newHomeChain_withFinalizeMigration() public { + // Initiate migration IN on NEW home chain + vm.prank(alice); + cHub.initiateMigration(newHomeDef, initiateMigrationState); + + // Verify channel is in MIGRATING_IN status + verifyChannelData( + newHomeChannelId, + ChannelStatus.MIGRATING_IN, + initiateMigrationVersion, + 0, + "newHomeInitiateMigrationState should be enforced" + ); + + // Challenge with newer FINALIZE_MIGRATION state + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(newHomeChannelId, finalizeMigrationState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(newHomeChannelId, finalizeMigrationState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and finalizeMigrationState was enforced + verifyChannelData( + newHomeChannelId, + ChannelStatus.DISPUTED, + finalizeMigrationVersion, + block.timestamp + CHALLENGE_DURATION, + "finalizeMigrationState should start a challenge" + ); + verifyChannelState( + newHomeChannelId, + [uint256(500), uint256(0)], + [int256(0), int256(500)], + "finalizeMigrationState should be enforced" + ); + } +} +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_challenge/ChannelHub_challengeSessionKeyValidator.t.sol b/contracts/test/ChannelHub_challenge/ChannelHub_challengeSessionKeyValidator.t.sol new file mode 100644 index 000000000..4ccbedcba --- /dev/null +++ b/contracts/test/ChannelHub_challenge/ChannelHub_challengeSessionKeyValidator.t.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Challenge_Base} from "./ChannelHub_Challenge_Base.t.sol"; + +import {Utils} from "../../src/Utils.sol"; +import {TestUtils, SESSION_KEY_VALIDATOR_ID} from "../TestUtils.sol"; +import {ChannelDefinition, State, StateIntent, Ledger, ParticipantIndex} from "../../src/interfaces/Types.sol"; +import {SessionKeyValidator, SessionKeyAuthorization} from "../../src/sigValidators/SessionKeyValidator.sol"; + +/* + * @dev Black-box tests verifying that all three challenge functions revert with + * ChallengeWithSessionKeyNotSupported when the challenger signature uses the + * SessionKeyValidator. The channel/escrow must have the SessionKeyValidator + * approved (bit SESSION_KEY_VALIDATOR_ID set in approvedSignatureValidators) so + * that _extractValidator routes to the validator before the revert is triggered. + */ + +abstract contract ChannelHubTest_Challenge_SkApproved_Base is ChannelHubTest_Challenge_Base { + function setUp() public virtual override { + super.setUp(); + + def = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE, + // forge-lint: disable-next-line(incorrect-shift) -- intentional bitmask: 1 << N sets bit N + approvedSignatureValidators: 1 << SESSION_KEY_VALIDATOR_ID, + metadata: bytes32(0) + }); + channelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + } + + function buildSkChallengerSig(bytes32 channelId_, State memory state, uint256 signerPk, address skAddress) + internal + pure + returns (bytes memory) + { + bytes32 metadataHash = keccak256("metadata"); + SessionKeyAuthorization memory skAuth = TestUtils.buildAndSignSkAuth(vm, skAddress, metadataHash, signerPk); + // no need to change challenge signature in any way — just well-formatted would suffice + return TestUtils.signStateEip191WithSkValidator(vm, channelId_, state, ALICE_SK1_PK, skAuth); + } +} + +// forge-lint: disable-start(unsafe-typecast) + +// ───────────────────────────────────────────────────────────────────────────── +// challengeChannel +// ───────────────────────────────────────────────────────────────────────────── + +contract ChannelHubTest_Challenge_HomeChain_SessionKeyValidator is ChannelHubTest_Challenge_SkApproved_Base { + function setUp() public override { + super.setUp(); + createChannelWithDeposit(); + } + + function test_revert_challengeChannel_withSkValidator_asUser() public { + bytes memory challengerSig = buildSkChallengerSig(channelId, initState, ALICE_PK, aliceSk1); + + vm.prank(alice); + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.USER); + } + + function test_revert_challengeChannel_withSkValidator_asNode() public { + bytes memory challengerSig = buildSkChallengerSig(channelId, initState, NODE_PK, aliceSk1); + + vm.prank(node); + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// challengeEscrowDeposit +// ───────────────────────────────────────────────────────────────────────────── + +contract ChannelHubTest_Challenge_NonHomeChain_EscrowDeposit_SessionKeyValidator is + ChannelHubTest_Challenge_SkApproved_Base +{ + uint64 constant ESCROW_VERSION = 1; + uint256 constant ESCROW_AMOUNT = 500; + + bytes32 escrowId; + State initiateEscrowDepositState; + + function setUp() public override { + super.setUp(); + + // Non-home chain: NON_HOME_CHAIN_ID (42) is the home chain, block.chainid is the non-home chain. + // No on-chain channel exists on the current chain — initiateEscrowDeposit takes the non-home path. + initiateEscrowDepositState = State({ + version: ESCROW_VERSION, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: 500, + userNetFlow: 500, + nodeAllocation: ESCROW_AMOUNT, + nodeNetFlow: int256(ESCROW_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + 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); + } + + function test_revert_challengeEscrowDeposit_withSkValidator_asUser() public { + bytes memory challengerSig = buildSkChallengerSig(channelId, initiateEscrowDepositState, ALICE_PK, aliceSk1); + + vm.prank(alice); + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.USER); + } + + function test_revert_challengeEscrowDeposit_withSkValidator_asNode() public { + bytes memory challengerSig = buildSkChallengerSig(channelId, initiateEscrowDepositState, NODE_PK, aliceSk1); + + vm.prank(node); + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.NODE); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// challengeEscrowWithdrawal +// ───────────────────────────────────────────────────────────────────────────── + +contract ChannelHubTest_Challenge_NonHomeChain_EscrowWithdrawal_SessionKeyValidator is + ChannelHubTest_Challenge_SkApproved_Base +{ + uint64 constant WITHDRAWAL_VERSION = 1; + uint256 constant WITHDRAWAL_AMOUNT = 300; + + bytes32 escrowId; + State initiateEscrowWithdrawalState; + + function setUp() public override { + super.setUp(); + + // Non-home chain: NON_HOME_CHAIN_ID (42) is the home chain, block.chainid is the non-home chain. + initiateEscrowWithdrawalState = State({ + version: WITHDRAWAL_VERSION, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + 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: WITHDRAWAL_AMOUNT, + 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); + } + + function test_revert_challengeEscrowWithdrawal_withSkValidator_asUser() public { + bytes memory challengerSig = buildSkChallengerSig(channelId, initiateEscrowWithdrawalState, ALICE_PK, aliceSk1); + + vm.prank(alice); + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.USER); + } + + function test_revert_challengeEscrowWithdrawal_withSkValidator_asNode() public { + bytes memory challengerSig = buildSkChallengerSig(channelId, initiateEscrowWithdrawalState, NODE_PK, aliceSk1); + + vm.prank(node); + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.NODE); + } +} +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_challengeNonHomeChain.t.sol b/contracts/test/ChannelHub_challengeNonHomeChain.t.sol deleted file mode 100644 index 94c525641..000000000 --- a/contracts/test/ChannelHub_challengeNonHomeChain.t.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.30; - -import {ChannelHubTest_Base} from "./ChannelHub_Base.t.sol"; - -contract ChannelHubTest_Challenge_NonHomeChain_EscrowDeposit is ChannelHubTest_Base { - /* - - 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 funds can be withdrawn after `challengeExpireAt` time passes - - 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 - */ - - } - -contract ChannelHubTest_Challenge_NonHomeChain_EscrowWithdrawal is ChannelHubTest_Base { - /* - - escrow withdrawal can be challenged - - challenged escrow withdrawal funds can be withdrawn after `challengeExpireAt` time passes - - 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 - */ - - } - -contract ChannelHubTest_Challenge_NonHomeChain_Migration is ChannelHubTest_Base { - /* - - a channel in earlier state can be challenged with initiated migration state - - a channel in initiated migration state can be challenged with it - - a channel in earlier state can be challenged with finalize migration state - - a channel in finalize migration state can be challenged with it - */ - - } diff --git a/contracts/test/ChannelHub_claimFunds.t.sol b/contracts/test/ChannelHub_claimFunds.t.sol index a1bb3886a..227c8eeb6 100644 --- a/contracts/test/ChannelHub_claimFunds.t.sol +++ b/contracts/test/ChannelHub_claimFunds.t.sol @@ -24,7 +24,7 @@ contract ChannelHubTest_claimFunds is Test { uint256 constant BALANCE_AMOUNT = RECLAIM_AMOUNT * 10; function setUp() public { - cHub = new TestChannelHub(new ECDSAValidator()); + cHub = new TestChannelHub(new ECDSAValidator(), makeAddr("node")); token = new MockERC20("Test Token", "TST", 18); revertingReceiver = new RevertingEthReceiver(); diff --git a/contracts/test/ChannelHub_emitsNodeBalanceUpdated.t.sol b/contracts/test/ChannelHub_emitsNodeBalanceUpdated.t.sol new file mode 100644 index 000000000..f44940853 --- /dev/null +++ b/contracts/test/ChannelHub_emitsNodeBalanceUpdated.t.sol @@ -0,0 +1,599 @@ +// 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 {TestUtils} from "./TestUtils.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 getNodeBalance(). 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: + * - depositToNode — direct node deposit by node + * - withdrawFromNode — direct node 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,uint256)"); + + // Non-home chain constants (fake foreign chain) + uint64 constant FOREIGN_CHAIN_ID = 42; + address constant FOREIGN_TOKEN = address(42); + + // ======== 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(token, expectedBalance) emission. + function _expectEmitNodeBalanceUpdated(uint256 expectedBalance) internal { + vm.expectEmit(true, true, true, true, address(cHub)); + emit ChannelHub.NodeBalanceUpdated(address(token), 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: TestUtils.emptyLedger(), + 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: TestUtils.emptyLedger(), + 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_onDepositToNode() public { + token.mint(node, DEPOSIT_AMOUNT); + vm.startPrank(node); + token.approve(address(cHub), DEPOSIT_AMOUNT); + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE + DEPOSIT_AMOUNT); + cHub.depositToNode(address(token), DEPOSIT_AMOUNT); + vm.stopPrank(); + + assertEq(cHub.getNodeBalance(address(token)), INITIAL_BALANCE + DEPOSIT_AMOUNT); + } + + function test_success_onWithdrawFromNode() public { + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(node); + cHub.withdrawFromNode(node, address(token), DEPOSIT_AMOUNT); + + assertEq(cHub.getNodeBalance(address(token)), 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(alice); + cHub.createChannel(def, state); + + assertEq(cHub.getNodeBalance(address(token)), 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(alice); + cHub.createChannel(def, state); + + assertEq(cHub.getNodeBalance(address(token)), 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 = TestUtils.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.getNodeBalance(address(token)), 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: TestUtils.emptyLedger(), + 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.getNodeBalance(address(token)), expectedBalance); + } + + function test_success_onCheckpointChannel_withNodeFundChange() public { + State memory prevState = _createSimpleChannel(); + + // Off-chain: user transferred 500 to node. + State memory candidate = TestUtils.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.getNodeBalance(address(token)), 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE); + vm.prank(alice); + cHub.closeChannel(channelId, candidate); + + assertEq(cHub.getNodeBalance(address(token)), 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 = TestUtils.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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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 = TestUtils.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.getNodeBalance(address(token)), 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 = TestUtils.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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), 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.getNodeBalance(address(token)), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } +} +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_EscrowDepositPurge_Base.t.sol b/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_EscrowDepositPurge_Base.t.sol new file mode 100644 index 000000000..bc43da331 --- /dev/null +++ b/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_EscrowDepositPurge_Base.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; + +import {TestChannelHub} from "../TestChannelHub.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {ECDSAValidator} from "../../src/sigValidators/ECDSAValidator.sol"; +import {EscrowStatus} from "../../src/interfaces/Types.sol"; + +abstract contract ChannelHubTest_EscrowDepositPurge_Base is Test { + TestChannelHub public cHub; + MockERC20 public token; + + address public node; + address public user; + + uint256 private _escrowCounter; + + uint64 internal constant UNLOCK_DELAY = 3 hours; + uint256 internal constant LOCKED_AMOUNT = 500; + + function setUp() public virtual { + node = makeAddr("node"); + user = makeAddr("user"); + + cHub = new TestChannelHub(new ECDSAValidator(), node); + token = new MockERC20("Test Token", "TST", 18); + } + + /// @dev Creates a unique escrow ID for each call, deterministic per test + function _nextEscrowId() internal returns (bytes32) { + return bytes32(++_escrowCounter); + } + + /// @dev Adds a single escrow entry to the hub storage and the purge queue + function _addEscrow(EscrowStatus status, uint64 unlockAt, uint64 challengeExpireAt, uint256 lockedAmount) + internal + returns (bytes32 escrowId) + { + escrowId = _nextEscrowId(); + cHub.workaround_setEscrowDeposit( + escrowId, bytes32(0), status, user, node, unlockAt, challengeExpireAt, lockedAmount, address(token) + ); + cHub.workaround_addEscrowDepositId(escrowId); + } + + /// @dev Shorthand: INITIALIZED escrow with unlock time in the past (purgeable) + function _addUnlockable(uint256 lockedAmount) internal returns (bytes32) { + return _addEscrow(EscrowStatus.INITIALIZED, uint64(block.timestamp) - 1, 0, lockedAmount); + } + + /// @dev Shorthand: INITIALIZED escrow with unlock time in the future (not yet purgeable) + function _addNotYetUnlockable(uint256 lockedAmount) internal returns (bytes32) { + return _addEscrow(EscrowStatus.INITIALIZED, uint64(block.timestamp) + UNLOCK_DELAY, 0, lockedAmount); + } + + /// @dev Shorthand: FINALIZED escrow + function _addFinalized() internal returns (bytes32) { + return _addEscrow(EscrowStatus.FINALIZED, 0, 0, 0); + } + + /// @dev Shorthand: DISPUTED escrow (challenge still active) + function _addDisputed(uint64 challengeExpireAt) internal returns (bytes32) { + return _addEscrow(EscrowStatus.DISPUTED, uint64(block.timestamp) - 1, challengeExpireAt, LOCKED_AMOUNT); + } +} diff --git a/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_getUnlockableEscrowDepositStats.t.sol b/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_getUnlockableEscrowDepositStats.t.sol new file mode 100644 index 000000000..2fa297cfd --- /dev/null +++ b/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_getUnlockableEscrowDepositStats.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_EscrowDepositPurge_Base} from "./ChannelHub_EscrowDepositPurge_Base.t.sol"; + +contract ChannelHubTest_getUnlockableEscrowDepositStats is ChannelHubTest_EscrowDepositPurge_Base { + function _getAndAssertStats(uint256 expectedCount, uint256 expectedTotal, string memory message) internal view { + (uint256 count, uint256 totalAmount) = cHub.getUnlockableEscrowDepositStats(); + assertEq(count, expectedCount, message); + assertEq(totalAmount, expectedTotal, message); + } + + // ========== Empty queue ========== + + function test_returns_zero_forEmptyQueue() public view { + _getAndAssertStats(0, 0, "Empty queue should return zeroes"); + } + + // ========== Single-entry queue ========== + + function test_returns_zero_whenSingleInitialized_notYetUnlockable() public { + _addNotYetUnlockable(LOCKED_AMOUNT); + + _getAndAssertStats(0, 0, "Single initialized not yet unlockable should return zeroes"); + } + + function test_returns_one_whenSingleInitialized_unlockable() public { + _addUnlockable(LOCKED_AMOUNT); + + _getAndAssertStats(1, LOCKED_AMOUNT, "Single initialized unlockable should return one count and amount"); + } + + function test_returns_zero_whenSingleFinalized() public { + _addFinalized(); + + _getAndAssertStats(0, 0, "Single finalized should return zeroes"); + } + + function test_returns_zero_whenSingleDisputed() public { + _addDisputed(uint64(block.timestamp) + 1 days); + + _getAndAssertStats(0, 0, "Single disputed should return zeroes"); + } + + // ========== FINALIZED / DISPUTED blocking ========== + + function test_skipsFinalized_andCountsSubsequentUnlockable() public { + _addFinalized(); + _addUnlockable(LOCKED_AMOUNT); + + _getAndAssertStats(1, LOCKED_AMOUNT, "Should skip finalized and count subsequent unlockable"); + } + + function test_skipsDisputed_andCountsSubsequentUnlockable() public { + _addDisputed(uint64(block.timestamp) + 1 days); + _addUnlockable(LOCKED_AMOUNT); + + _getAndAssertStats(1, LOCKED_AMOUNT, "Should skip disputed and count subsequent unlockable"); + } + + function test_skipsFinalizedAndDisputed_andCountsSubsequentUnlockable() public { + _addFinalized(); + _addDisputed(uint64(block.timestamp) + 1 days); + _addUnlockable(LOCKED_AMOUNT); + + _getAndAssertStats(1, LOCKED_AMOUNT, "Should skip finalized and disputed, and count subsequent unlockable"); + } + + // ========== Stop condition ========== + + function test_stopsAtNonUnlockable_afterCountingUnlockable() public { + _addUnlockable(LOCKED_AMOUNT); + _addNotYetUnlockable(LOCKED_AMOUNT * 2); + + _getAndAssertStats(1, LOCKED_AMOUNT, "Should count first unlockable and stop at subsequent non-unlockable"); + } + + // ========== Multiple entries ========== + + function test_countsAllUnlockable_inMultiEntryQueue() public { + uint256 amount1 = 100; + uint256 amount2 = 200; + uint256 amount3 = 300; + + _addUnlockable(amount1); + _addUnlockable(amount2); + _addUnlockable(amount3); + + _getAndAssertStats(3, amount1 + amount2 + amount3, "Should count all unlockable entries in multi-entry queue"); + } + + // ========== escrowHead offset ========== + + /// @dev After the purge advances escrowHead past the first entry, stats must start from the new head + function test_startsFromEscrowHead_ignoringAlreadyAdvancedEntries() public { + // Position 0: unlockable — will be consumed by the purge call below + _addUnlockable(LOCKED_AMOUNT); + // Position 1: not yet unlockable — will be the first visible entry after head advances + _addNotYetUnlockable(LOCKED_AMOUNT); + + // Advance escrowHead to 1 by purging position 0 + cHub.harness_purgeEscrowDeposits(1); + assertEq(cHub.escrowHead(), 1); + + _getAndAssertStats(0, 0, "After advancing head, should return zeroes since next entry is not unlockable"); + } +} diff --git a/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_purgeEscrowDeposits.t.sol b/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_purgeEscrowDeposits.t.sol new file mode 100644 index 000000000..701a8d101 --- /dev/null +++ b/contracts/test/ChannelHub_escrowDepositPurge/ChannelHub_purgeEscrowDeposits.t.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_EscrowDepositPurge_Base} from "./ChannelHub_EscrowDepositPurge_Base.t.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {EscrowStatus} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_purgeEscrowDeposits is ChannelHubTest_EscrowDepositPurge_Base { + function _purge(uint256 maxToPurge) internal { + cHub.harness_purgeEscrowDeposits(maxToPurge); + } + + function _assertEscrowHead(uint256 expected, string memory message) internal view { + assertEq(cHub.escrowHead(), expected, message); + } + + function _assertNodeBalance(address token_, uint256 expected, string memory message) internal view { + assertEq(cHub.getNodeBalance(token_), expected, message); + } + + function _assertNodeBalance(uint256 expected, string memory message) internal view { + _assertNodeBalance(address(token), expected, message); + } + + function _assertEscrowStatus(bytes32 escrowId, EscrowStatus expected, string memory message) internal view { + (, EscrowStatus status,,,,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(status), uint8(expected), message); + } + + function _assertEscrowLockedAmount(bytes32 escrowId, uint256 expected, string memory message) internal view { + (,,,, uint256 lockedAmount,) = cHub.getEscrowDepositData(escrowId); + assertEq(lockedAmount, expected, message); + } + + // ========== Empty queue ========== + + function test_doesNothing_forEmptyQueue() public { + _purge(type(uint256).max); + + _assertEscrowHead(0, "Head should stay at 0 for empty queue"); + } + + // ========== Single entry ========== + + function test_doesNotPurge_whenSingleInitialized_notYetUnlockable() public { + bytes32 id = _addNotYetUnlockable(LOCKED_AMOUNT); + + _purge(type(uint256).max); + + _assertEscrowHead(0, "Head should stay at 0"); + _assertEscrowStatus(id, EscrowStatus.INITIALIZED, "Status should remain INITIALIZED"); + _assertNodeBalance(0, "Node balance should be unchanged"); + } + + function test_purges_whenSingleInitialized_unlockable() public { + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.NodeBalanceUpdated(address(token), LOCKED_AMOUNT); + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); + + _purge(type(uint256).max); + + _assertEscrowHead(1, "Head should advance to 1"); + _assertEscrowStatus(id, EscrowStatus.FINALIZED, "Status should be FINALIZED"); + _assertEscrowLockedAmount(id, 0, "Locked amount should be zeroed after purge"); + _assertNodeBalance(LOCKED_AMOUNT, "Node balance should be credited with locked amount"); + } + + function test_skips_whenSingleFinalized() public { + _addFinalized(); + + _purge(type(uint256).max); + + _assertEscrowHead(1, "Head should advance past FINALIZED"); + _assertNodeBalance(0, "Node balance should be unchanged"); + } + + function test_skips_whenSingleDisputed() public { + _addDisputed(uint64(block.timestamp) + 1 days); + + _purge(type(uint256).max); + + _assertEscrowHead(1, "Head should advance past DISPUTED"); + _assertNodeBalance(0, "Node balance should be unchanged"); + } + + // ========== Multiple entries ========== + + function test_skipsDisputed_andPurgesSubsequentUnlockable() public { + _addDisputed(uint64(block.timestamp) + 1 days); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); + + _purge(type(uint256).max); + + _assertEscrowHead(2, "Head should advance past both entries"); + _assertEscrowStatus(id, EscrowStatus.FINALIZED, "Unlockable entry after DISPUTED should be purged"); + _assertNodeBalance(LOCKED_AMOUNT, "Node balance should reflect the purged unlockable entry"); + } + + function test_skipsFinalized_andPurgesSubsequentUnlockable() public { + _addFinalized(); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); + + _purge(type(uint256).max); + + _assertEscrowHead(2, "Head should advance past both entries"); + _assertEscrowStatus(id, EscrowStatus.FINALIZED, "Unlockable entry after FINALIZED should be purged"); + _assertNodeBalance(LOCKED_AMOUNT, "Node balance should reflect the purged unlockable entry"); + } + + function test_stopsAtNonUnlockable_afterPurgingUnlockable() public { + bytes32 id1 = _addUnlockable(LOCKED_AMOUNT); + bytes32 id2 = _addNotYetUnlockable(LOCKED_AMOUNT * 2); + + _purge(type(uint256).max); + + _assertEscrowHead(1, "Head should stop at the not-yet-unlockable entry"); + _assertEscrowStatus(id1, EscrowStatus.FINALIZED, "First entry should be FINALIZED"); + _assertEscrowStatus(id2, EscrowStatus.INITIALIZED, "Second entry should remain INITIALIZED"); + _assertNodeBalance(LOCKED_AMOUNT, "Node balance should reflect only the first purged entry"); + } + + function test_purgesAll_inMultiUnlockableQueue() public { + uint256 amount1 = 100; + uint256 amount2 = 200; + uint256 amount3 = 300; + + bytes32 id1 = _addUnlockable(amount1); + bytes32 id2 = _addUnlockable(amount2); + bytes32 id3 = _addUnlockable(amount3); + + bytes32[] memory expectedIds = new bytes32[](3); + expectedIds[0] = id1; + expectedIds[1] = id2; + expectedIds[2] = id3; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 3); + + _purge(type(uint256).max); + + _assertEscrowHead(3, "Head should advance past all three entries"); + _assertNodeBalance(amount1 + amount2 + amount3, "Node balance should reflect all purged amounts"); + } + + // ========== maxSteps limit ========== + + // Trivial case: all entries are UNLOCKABLE, so steps == purgedCount. + function test_respectsMaxSteps_stopsAfterLimit_allUnlockable() public { + bytes32 id1 = _addUnlockable(LOCKED_AMOUNT); + bytes32 id2 = _addUnlockable(LOCKED_AMOUNT); + bytes32 id3 = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](2); + expectedIds[0] = id1; + expectedIds[1] = id2; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 2); + + _purge(2); + + _assertEscrowHead(2, "Head should advance by exactly maxSteps"); + _assertEscrowStatus(id1, EscrowStatus.FINALIZED, "First entry should be purged"); + _assertEscrowStatus(id2, EscrowStatus.FINALIZED, "Second entry should be purged"); + _assertEscrowStatus(id3, EscrowStatus.INITIALIZED, "Third entry should remain INITIALIZED"); + _assertNodeBalance(LOCKED_AMOUNT * 2, "Node balance should reflect only the two purged entries"); + } + + function test_disputedSkip_consumesStep_preventsReachingUnlockable() public { + _addDisputed(uint64(block.timestamp) + 1 days); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + _purge(1); + + _assertEscrowHead(1, "DISPUTED skip consumed the only step; head stopped after it"); + _assertEscrowStatus(id, EscrowStatus.INITIALIZED, "UNLOCKABLE not reached within step budget"); + _assertNodeBalance(0, "No purge occurred"); + } + + function test_disputedSkipWithOnePurge_consumesStep_withBudgetOfTwo() public { + _addDisputed(uint64(block.timestamp) + 1 days); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); // purgedCount=1, not steps=2 + + _purge(2); + + _assertEscrowHead(2, "FINALIZED skip consumed the first step; first UNLOCKABLE purge consumed the second step"); + _assertEscrowStatus(id, EscrowStatus.FINALIZED, "UNLOCKABLE was purged"); + _assertNodeBalance(LOCKED_AMOUNT, "Node credited for one purge"); + } + + function test_finalizedSkip_consumesStep_preventsReachingUnlockable() public { + _addFinalized(); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + _purge(1); + + _assertEscrowHead(1, "FINALIZED skip consumed the only step; head stopped after it"); + _assertEscrowStatus(id, EscrowStatus.INITIALIZED, "UNLOCKABLE not reached within step budget"); + _assertNodeBalance(0, "No purge occurred"); + } + + function test_finalizedSkipWithOnePurge_consumesStep_withBudgetOfTwo() public { + _addFinalized(); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); // purgedCount=1, not steps=2 + + _purge(2); + + _assertEscrowHead(2, "FINALIZED skip consumed the first step; first UNLOCKABLE purge consumed the second step"); + _assertEscrowStatus(id, EscrowStatus.FINALIZED, "UNLOCKABLE was purged"); + _assertNodeBalance(LOCKED_AMOUNT, "Node credited for one purge"); + } + + function test_twoFinalizedSkips_exhaustBudgetOfTwo_unlockableNotReached() public { + _addFinalized(); + _addFinalized(); + bytes32 id = _addUnlockable(LOCKED_AMOUNT); + + _purge(2); + + _assertEscrowHead(2, "Both steps consumed by FINALIZED skips"); + _assertEscrowStatus(id, EscrowStatus.INITIALIZED, "UNLOCKABLE not reached within step budget"); + _assertNodeBalance(0, "No purge occurred"); + } + + function test_finalizedSkipPlusOnePurge_withBudgetOfTwo() public { + _addFinalized(); + bytes32 id1 = _addUnlockable(LOCKED_AMOUNT); + bytes32 id2 = _addUnlockable(LOCKED_AMOUNT); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id1; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); // purgedCount=1, not steps=2 + + _purge(2); + + _assertEscrowHead(2, "Step 1 = FINALIZED skip, step 2 = first UNLOCKABLE purge"); + _assertEscrowStatus(id1, EscrowStatus.FINALIZED, "First UNLOCKABLE was purged"); + _assertEscrowStatus(id2, EscrowStatus.INITIALIZED, "Second UNLOCKABLE not reached"); + _assertNodeBalance(LOCKED_AMOUNT, "Only one purge credited to node"); + } + + // ========== Mixed queue ========== + + function test_skipsFinalizedAndDisputed_purgesUnlockable_stopsAtNonUnlockable() public { + _addFinalized(); + _addDisputed(uint64(block.timestamp) + 1 days); + bytes32 id3 = _addUnlockable(LOCKED_AMOUNT); + bytes32 id4 = _addNotYetUnlockable(LOCKED_AMOUNT * 2); + + bytes32[] memory expectedIds = new bytes32[](1); + expectedIds[0] = id3; + + vm.expectEmit(true, true, true, true); + emit ChannelHub.EscrowDepositsPurged(expectedIds, 1); + + _purge(type(uint256).max); + + _assertEscrowHead(3, "Head should stop at the not-yet-unlockable entry"); + _assertEscrowStatus(id3, EscrowStatus.FINALIZED, "Third entry should be purged"); + _assertEscrowStatus(id4, EscrowStatus.INITIALIZED, "Fourth entry should remain INITIALIZED"); + _assertNodeBalance(LOCKED_AMOUNT, "Node balance should reflect only the one purged entry"); + } + + // ========== Node balance accuracy ========== + + function test_creditsCorrectToken_whenMultipleTokensExist() public { + MockERC20 token2 = new MockERC20("Token2", "TK2", 18); + + // token2 escrow is unlockable; default token escrow is not yet unlockable + bytes32 token2EscrowId = _nextEscrowId(); + cHub.workaround_setEscrowDeposit( + token2EscrowId, + bytes32(0), + EscrowStatus.INITIALIZED, + user, + node, + uint64(block.timestamp) - 1, + 0, + LOCKED_AMOUNT, + address(token2) + ); + cHub.workaround_addEscrowDepositId(token2EscrowId); + + _addNotYetUnlockable(LOCKED_AMOUNT); + + _purge(type(uint256).max); + + _assertNodeBalance(0, "Default token balance should be unchanged"); + _assertNodeBalance(address(token2), LOCKED_AMOUNT, "NODE balance should be credited for token2"); + } +} diff --git a/contracts/test/ChannelHub_crosschain.lifecycle.t.sol b/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain.lifecycle.t.sol similarity index 91% rename from contracts/test/ChannelHub_crosschain.lifecycle.t.sol rename to contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain.lifecycle.t.sol index d7f3f6502..2d4578040 100644 --- a/contracts/test/ChannelHub_crosschain.lifecycle.t.sol +++ b/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain.lifecycle.t.sol @@ -1,11 +1,19 @@ // 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"; +import {TestUtils} from "../TestUtils.sol"; // forge-lint: disable-next-item(unsafe-typecast) contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { @@ -83,11 +91,11 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1000, "User balance after channel creation"); // transfer 42 (allocation decreases by 42, node net flow decreases by 42) - state = nextState(state, StateIntent.OPERATE, [uint256(958), uint256(0)], [int256(1000), int256(-42)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(958), uint256(0)], [int256(1000), int256(-42)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // deposit from another chain - state = nextState( + state = TestUtils.nextState( state, StateIntent.INITIATE_ESCROW_DEPOSIT, // user amounts stay the same, node amounts increase by 500 @@ -107,14 +115,14 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // initiate escrow deposit on home chain // Expected: user allocation = 958, user net flow = 1000, node allocation = 0, node net flow = -42 - vm.prank(alice); + vm.prank(node); cHub.initiateEscrowDeposit(def, state); verifyChannelState( channelId, [uint256(958), uint256(500)], [int256(1000), int256(458)], "after cross chain deposit" ); // finalize escrow deposit - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_ESCROW_DEPOSIT, // user allocation amount increases by cross-chain deposit, node allocation goes to 0 @@ -129,16 +137,19 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 24 (allocation increases by 24, node net flow increases by 24) - state = nextState(state, StateIntent.OPERATE, [uint256(1482), uint256(0)], [int256(1000), int256(482)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1482), uint256(0)], [int256(1000), int256(482)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // send 12 (allocation decreases by 12, node net flow decreases by 12) - state = nextState(state, StateIntent.OPERATE, [uint256(1470), uint256(0)], [int256(1000), int256(470)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1470), uint256(0)], [int256(1000), int256(470)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // withdraw 250 on home chain // Expected: user allocation = 1220, user net flow = 750, node allocation = 0, node net flow = 470 - state = nextState(state, StateIntent.WITHDRAW, [uint256(1220), uint256(0)], [int256(750), int256(470)]); + state = + TestUtils.nextState(state, StateIntent.WITHDRAW, [uint256(1220), uint256(0)], [int256(750), int256(470)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); vm.prank(alice); @@ -149,19 +160,19 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 750, "User balance after withdrawal"); // send 2 (allocation decreases by 2, node net flow decreases by 2) - state = nextState(state, StateIntent.OPERATE, [uint256(1218), uint256(0)], [int256(750), int256(468)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1218), uint256(0)], [int256(750), int256(468)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 3 (allocation increases by 3, node net flow increases by 3) - state = nextState(state, StateIntent.OPERATE, [uint256(1221), uint256(0)], [int256(750), int256(471)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1221), uint256(0)], [int256(750), int256(471)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // send 4 (allocation decreases by 4, node net flow decreases by 4) - state = nextState(state, StateIntent.OPERATE, [uint256(1217), uint256(0)], [int256(750), int256(467)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1217), uint256(0)], [int256(750), int256(467)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // withdrawal to another chain - state = nextState( + state = TestUtils.nextState( state, StateIntent.INITIATE_ESCROW_WITHDRAWAL, // home chain stays the same @@ -180,7 +191,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // NOTE: see a `test_withdrawalEscrow_nonHomeChain` test for that // finalize escrow withdrawal on another chain - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_ESCROW_WITHDRAWAL, // user allocation decreases by withdrawal amount, node allocation stays 0, node net flow decreases by withdrawal amount @@ -195,7 +206,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 10 (allocation increases by 10, node net flow increases by 10) - state = nextState(state, StateIntent.OPERATE, [uint256(477), uint256(0)], [int256(750), int256(-273)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(477), uint256(0)], [int256(750), int256(-273)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // checkpoint on home chain @@ -207,19 +218,19 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 750, "User balance after checkpoint"); // send 9 (allocation decreases by 9, node net flow decreases by 9) - state = nextState(state, StateIntent.OPERATE, [uint256(468), uint256(0)], [int256(750), int256(-282)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(468), uint256(0)], [int256(750), int256(-282)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 8 (allocation increases by 8, node net flow increases by 8) - state = nextState(state, StateIntent.OPERATE, [uint256(476), uint256(0)], [int256(750), int256(-274)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(476), uint256(0)], [int256(750), int256(-274)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // send 7 (allocation decreases by 7, node net flow decreases by 7) - state = nextState(state, StateIntent.OPERATE, [uint256(469), uint256(0)], [int256(750), int256(-281)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(469), uint256(0)], [int256(750), int256(-281)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // migrate channel - state = nextState( + state = TestUtils.nextState( state, StateIntent.INITIATE_MIGRATION, // home chain stays the same @@ -238,7 +249,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // NOTE: see a `test_migration_nonHomeChain` test for that // finalize migration on old home chain - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_MIGRATION, // channel closes on old home chain, allocations go to 0, net flows balance out @@ -341,10 +352,10 @@ 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.getNodeBalance(address(token)); // state from the "happyPath" test, but with home and nonHome states swapped - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_ESCROW_DEPOSIT, [uint256(1458), uint256(0)], @@ -363,7 +374,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // 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.getNodeBalance(address(token)); assertEq(nodeBalanceAfter, nodeBalanceBefore + 500, "Node balance after escrow deposit finalized"); // Verify escrow struct is updated on ChannelsHub @@ -454,10 +465,10 @@ 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.getNodeBalance(address(token14dec)); // After finalization, home chain user allocation increases, non-home releases funds to node - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_ESCROW_DEPOSIT, [uint256(60 * 1e6), uint256(0)], // Home: user allocation increases by 10 USDC @@ -477,7 +488,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { 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.getNodeBalance(address(token14dec)); assertEq(nodeBalanceAfter, nodeBalanceBefore + 10 * 1e14, "Node balance after escrow deposit finalized"); // Verify escrow struct is updated on ChannelsHub @@ -495,7 +506,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.getNodeBalance(address(token)); // state from the "happyPath" test, but with home and nonHome states swapped State memory state = State({ @@ -535,7 +546,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.getNodeBalance(address(token)); assertEq(nodeBalanceAfter, nodeBalanceBefore - 750, "Node balance after escrow withdrawal"); // Verify escrow struct is updated on ChannelsHub: escrow data exists, `locked` equals to withdrawalAmount @@ -549,7 +560,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { uint256 bobBalanceBefore = token.balanceOf(bob); // finalize escrow withdrawal on another chain - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_ESCROW_WITHDRAWAL, [uint256(467), uint256(0)], @@ -589,10 +600,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.depositToNode(address(token8dec), 100 * 1e8); vm.stopPrank(); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token8dec)); + uint256 nodeBalanceBefore = cHub.getNodeBalance(address(token8dec)); // Bob wants to withdraw 5 tokens on non-home chain (5e8 with 8 decimals = 5e2 with 2 decimals) State memory state = State({ @@ -633,7 +644,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.getNodeBalance(address(token8dec)); assertEq(nodeBalanceAfter, nodeBalanceBefore - 5 * 1e8, "Node balance after escrow withdrawal initiation"); // Verify escrow struct is created @@ -648,7 +659,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // Finalize escrow withdrawal on non-home chain // After withdrawal, user allocation on home decreases by 5 tokens (5e2 with 2 decimals) - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_ESCROW_WITHDRAWAL, [uint256(5 * 1e2), uint256(0)], // Home: user allocation decreased by 5 tokens @@ -681,7 +692,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.getNodeBalance(address(token)); uint256 userBalanceBefore = token.balanceOf(bob); // state from the "happyPath" test @@ -716,7 +727,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.getNodeBalance(address(token)); assertEq(nodeBalanceAfter, nodeBalanceBefore - 469, "Node balance after migration initiation"); // user balance should not have changed @@ -757,20 +768,20 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // perform some operations to verify channel is operating as normal // send 9 (allocation decreases by 9, node net flow decreases by 9) - state = nextState(state, StateIntent.OPERATE, [uint256(460), uint256(0)], [int256(0), int256(460)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(460), uint256(0)], [int256(0), int256(460)]); state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); // receive 8 (allocation increases by 8, node net flow increases by 8) - state = nextState(state, StateIntent.OPERATE, [uint256(468), uint256(0)], [int256(0), int256(468)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(468), uint256(0)], [int256(0), int256(468)]); state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); // send 7 (allocation decreases by 7, node net flow decreases by 7) - state = nextState(state, StateIntent.OPERATE, [uint256(461), uint256(0)], [int256(0), int256(461)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(461), uint256(0)], [int256(0), int256(461)]); state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); // withdraw 400 on home chain // Expected: user allocation = 61, user net flow = -400, node allocation = 0, node net flow = 461 - state = nextState(state, StateIntent.WITHDRAW, [uint256(61), uint256(0)], [int256(-400), int256(461)]); + state = TestUtils.nextState(state, StateIntent.WITHDRAW, [uint256(61), uint256(0)], [int256(-400), int256(461)]); state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); vm.prank(bob); @@ -796,7 +807,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.depositToNode(address(token10dec), 100 * 1e10); vm.stopPrank(); // 1. Create Channel with 10-decimal token on Old Home Chain @@ -837,7 +848,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // 2. Perform some operations to build up channel state // Transfer 5 tokens (allocation decreases, node net flow decreases) - state = nextState( + state = TestUtils.nextState( state, StateIntent.OPERATE, [uint256(45 * 1e10), uint256(0)], [int256(50 * 1e10), int256(-5 * 1e10)] ); state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); @@ -850,13 +861,13 @@ 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.depositToNode(address(token14dec), 100 * 1e14); vm.stopPrank(); // Initiate migration: Old home has 45 tokens (45e10 with 10 decimals) // New home will have 45 tokens (45e14 with 14 decimals) // Node must lock equivalent value: 45e10 in WAD = 45e18, 45e14 in WAD = 45e18 ✓ - state = nextState( + state = TestUtils.nextState( state, StateIntent.INITIATE_MIGRATION, [uint256(45 * 1e10), uint256(0)], // Old home stays the same @@ -881,7 +892,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // 4. Finalize Migration on Old Home Chain // After migration completes, allocations zero out on old home - state = nextState( + state = TestUtils.nextState( state, StateIntent.FINALIZE_MIGRATION, [uint256(0), uint256(0)], // Old home: allocations zero out diff --git a/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain_escrow_after_migration.lifecycle.t.sol b/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain_escrow_after_migration.lifecycle.t.sol new file mode 100644 index 000000000..806cb8c3d --- /dev/null +++ b/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain_escrow_after_migration.lifecycle.t.sol @@ -0,0 +1,312 @@ +// 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, + ChannelStatus, + EscrowStatus +} from "../../src/interfaces/Types.sol"; +import {TestUtils} from "../TestUtils.sol"; + +// These tests verify that escrow deposit and escrow withdrawal can be finalized on the non-home chain +// even after a migration has been initiated to that same chain (making it `MIGRATING_IN` and treated as +// home chain). Without the metadata-based routing fix, finalizeEscrowDeposit and +// finalizeEscrowWithdrawal would take the home chain path after migration, bypassing escrow metadata +// and permanently locking funds. +// +// The signing order in both tests reflects the realistic off-chain protocol scenario: +// the finalize state (v11) is pre-signed as the execution phase immediately after the initiate +// state (v10), before migration is ever signed. This means only ONE off-chain rule is broken +// (rule 21: node must not issue new states during a cross-chain op); version monotonicity is +// preserved in the signing sequence (v10 → v11 → v43). Migration is submitted on-chain before +// the pre-signed finalize is submitted, which triggers the bug the fix addresses. +// +// Signing order: v10 → v11 (pre-signed, not yet submitted) → v43 +// Submission order: v10 → v43 → v11 + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelHubTest_CrossChain_EscrowAfterMigration is ChannelHubTest_Base { + bytes32 bobChannelId; + ChannelDefinition bobDef; + + function setUp() public override { + super.setUp(); + + bobDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: bob, + node: node, + nonce: NONCE, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + + bobChannelId = Utils.getChannelId(bobDef, CHANNEL_HUB_VERSION); + } + + function test_depositEscrow_nonHomeChain_thenMigration() public { + // ====== Step 1: Initiate escrow deposit on non-home chain ====== + (ChannelStatus status,,,,) = cHub.getChannelData(bobChannelId); + assertEq(uint8(status), uint8(ChannelStatus.VOID), "Channel should be VOID before escrow"); + + uint256 bobBalanceBefore = token.balanceOf(bob); + uint256 nodeVaultBefore = cHub.getNodeBalance(address(token)); + + State memory escrowInitiateState = State({ + version: 10, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: 42, + token: address(42), + decimals: 18, + userAllocation: 958, + userNetFlow: 1000, + nodeAllocation: 500, + nodeNetFlow: 458 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 500, + userNetFlow: 500, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + escrowInitiateState = mutualSignStateBothWithEcdsaValidator(escrowInitiateState, bobChannelId, BOB_PK); + + bytes32 escrowId = Utils.getEscrowId(bobChannelId, escrowInitiateState.version); + + vm.prank(bob); + cHub.initiateEscrowDeposit(bobDef, escrowInitiateState); + + assertEq(token.balanceOf(bob), bobBalanceBefore - 500, "User balance after escrow deposit initiation"); + (, EscrowStatus escrowStatus,,, uint256 lockedAmount,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(escrowStatus), uint8(EscrowStatus.INITIALIZED), "Escrow should be INITIALIZED"); + assertEq(lockedAmount, 500, "Escrow locked amount should be 500"); + + // Pre-sign the finalize state (v11) as the execution phase — before migration is signed. + // This preserves version monotonicity in the signing sequence (v10 → v11 → v43). + // The state is not submitted yet; it will be submitted after migration is on-chain. + State memory escrowFinalizeState = TestUtils.nextState( + escrowInitiateState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [uint256(1458), uint256(0)], + [int256(1000), int256(458)], + uint64(block.chainid), + address(token), + 18, + [uint256(0), uint256(0)], + [int256(500), int256(-500)] + ); + escrowFinalizeState = mutualSignStateBothWithEcdsaValidator(escrowFinalizeState, bobChannelId, BOB_PK); + + // ====== Step 2: Initiate migration to this chain ====== + // Node breaks the "Flow suspension" rule by signing and submitting migration while escrow is pending. + // After this call, _isChannelHomeChain returns true for bobChannelId on the current chain, + // because the stored state has homeLedger.chainId == block.chainid (states are swapped on store). + State memory migrationState = State({ + version: 43, + intent: StateIntent.INITIATE_MIGRATION, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: 42, + token: address(42), + decimals: 18, + userAllocation: 469, + userNetFlow: 750, + nodeAllocation: 0, + nodeNetFlow: -281 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 469, + nodeNetFlow: 469 + }), + userSig: "", + nodeSig: "" + }); + migrationState = mutualSignStateBothWithEcdsaValidator(migrationState, bobChannelId, BOB_PK); + + vm.prank(bob); + cHub.initiateMigration(bobDef, migrationState); + + (status,,,,) = cHub.getChannelData(bobChannelId); + assertEq(uint8(status), uint8(ChannelStatus.MIGRATING_IN), "Channel should be MIGRATING_IN after migration"); + assertEq( + cHub.getNodeBalance(address(token)), + nodeVaultBefore - 469, + "Node vault after migration (locked 469 for migration)" + ); + + // ====== Step 3: Finalize escrow deposit ====== + // _isChannelHomeChain now returns true, but _isEscrowDepositHomeChain returns false because + // escrow metadata exists (channelId != 0). The non-home path is taken, correctly releasing + // the locked 500 back to the node vault. + // The finalize state was pre-signed before migration (see above); only the submission is here. + vm.warp(block.timestamp + cHub.ESCROW_DEPOSIT_UNLOCK_DELAY() + 1); + + uint256 nodeVaultBeforeFinalize = cHub.getNodeBalance(address(token)); + + vm.prank(node); + cHub.finalizeEscrowDeposit(bobChannelId, escrowId, escrowFinalizeState); + + assertEq(token.balanceOf(bob), bobBalanceBefore - 500, "User balance unchanged after escrow finalization"); + assertEq( + cHub.getNodeBalance(address(token)), + nodeVaultBeforeFinalize + 500, + "Node vault after escrow deposit finalization (500 returned)" + ); + + (, EscrowStatus finalStatus,,, uint256 finalLocked,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(finalStatus), uint8(EscrowStatus.FINALIZED), "Escrow should be FINALIZED"); + assertEq(finalLocked, 0, "Escrow locked amount should be 0"); + + // Non-home path does not update channel state + verifyChannelData(bobChannelId, ChannelStatus.MIGRATING_IN, 43, 0, "Channel should still be MIGRATING_IN"); + } + + function test_withdrawalEscrow_nonHomeChain_thenMigration() public { + // ====== Step 1: Initiate escrow withdrawal on non-home chain ====== + (ChannelStatus status,,,,) = cHub.getChannelData(bobChannelId); + assertEq(uint8(status), uint8(ChannelStatus.VOID), "Channel should be VOID before escrow"); + + uint256 bobBalanceBefore = token.balanceOf(bob); + uint256 nodeVaultBefore = cHub.getNodeBalance(address(token)); + + State memory escrowInitiateState = State({ + version: 10, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: 42, + token: address(42), + decimals: 18, + userAllocation: 1217, + userNetFlow: 750, + nodeAllocation: 0, + nodeNetFlow: 467 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 750, + nodeNetFlow: 750 + }), + userSig: "", + nodeSig: "" + }); + escrowInitiateState = mutualSignStateBothWithEcdsaValidator(escrowInitiateState, bobChannelId, BOB_PK); + + bytes32 escrowId = Utils.getEscrowId(bobChannelId, escrowInitiateState.version); + + vm.prank(bob); + cHub.initiateEscrowWithdrawal(bobDef, escrowInitiateState); + + assertEq( + cHub.getNodeBalance(address(token)), + nodeVaultBefore - 750, + "Node vault after escrow withdrawal initiation (locked 750)" + ); + (, EscrowStatus escrowStatus,, uint256 lockedAmount,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(escrowStatus), uint8(EscrowStatus.INITIALIZED), "Escrow should be INITIALIZED"); + assertEq(lockedAmount, 750, "Escrow locked amount should be 750"); + + // Pre-sign the finalize state (v11) as the execution phase — before migration is signed. + // This preserves version monotonicity in the signing sequence (v10 → v11 → v43). + // The state is not submitted yet; it will be submitted after migration is on-chain. + State memory escrowFinalizeState = TestUtils.nextState( + escrowInitiateState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [uint256(467), uint256(0)], + [int256(750), int256(-283)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [int256(-750), int256(750)] + ); + escrowFinalizeState = mutualSignStateBothWithEcdsaValidator(escrowFinalizeState, bobChannelId, BOB_PK); + + // ====== Step 2: Initiate migration to this chain ====== + // Node breaks the "Flow suspension" rule by signing and submitting migration while escrow is pending. + // After this call, _isChannelHomeChain returns true for bobChannelId on the current chain. + State memory migrationState = State({ + version: 43, + intent: StateIntent.INITIATE_MIGRATION, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: 42, + token: address(42), + decimals: 18, + userAllocation: 469, + userNetFlow: 750, + nodeAllocation: 0, + nodeNetFlow: -281 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 469, + nodeNetFlow: 469 + }), + userSig: "", + nodeSig: "" + }); + migrationState = mutualSignStateBothWithEcdsaValidator(migrationState, bobChannelId, BOB_PK); + + vm.prank(bob); + cHub.initiateMigration(bobDef, migrationState); + + (status,,,,) = cHub.getChannelData(bobChannelId); + assertEq(uint8(status), uint8(ChannelStatus.MIGRATING_IN), "Channel should be MIGRATING_IN after migration"); + assertEq( + cHub.getNodeBalance(address(token)), + nodeVaultBefore - 750 - 469, + "Node vault after migration (escrow 750 + migration 469)" + ); + + // ====== Step 3: Finalize escrow withdrawal ====== + // _isChannelHomeChain now returns true, but _isEscrowWithdrawalHomeChain returns false because + // escrow metadata exists (channelId != 0). The non-home path is taken, correctly pushing the + // locked 750 to the user. + // The finalize state was pre-signed before migration (see above); only the submission is here. + uint256 nodeVaultBeforeFinalize = cHub.getNodeBalance(address(token)); + + vm.prank(node); + cHub.finalizeEscrowWithdrawal(bobChannelId, escrowId, escrowFinalizeState); + + assertEq(token.balanceOf(bob), bobBalanceBefore + 750, "User balance after escrow withdrawal finalization"); + assertEq( + cHub.getNodeBalance(address(token)), + nodeVaultBeforeFinalize, + "Node vault unchanged after escrow withdrawal finalization (locked 750 went to user)" + ); + + (, EscrowStatus finalStatus,, uint256 finalLocked,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(finalStatus), uint8(EscrowStatus.FINALIZED), "Escrow should be FINALIZED"); + assertEq(finalLocked, 0, "Escrow locked amount should be 0"); + + // Non-home path does not update channel state; channel remains MIGRATING_IN + verifyChannelData(bobChannelId, ChannelStatus.MIGRATING_IN, 43, 0, "Channel should still be MIGRATING_IN"); + } +} diff --git a/contracts/test/ChannelHub_lifecycle/ChannelHub_exploits.t.sol b/contracts/test/ChannelHub_lifecycle/ChannelHub_exploits.t.sol new file mode 100644 index 000000000..69931fd8d --- /dev/null +++ b/contracts/test/ChannelHub_lifecycle/ChannelHub_exploits.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {TestUtils} from "../TestUtils.sol"; + +import {ChannelEngine} from "../../src/ChannelEngine.sol"; +import {Utils} from "../../src/Utils.sol"; +import {State, ChannelDefinition, StateIntent, Ledger} from "../../src/interfaces/Types.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +/** + * @dev Tests for critical exploits related to channel lifecycle and state transitions. + */ +contract ChannelHubTest_exploits is ChannelHubTest_Base { + // Regression test for the token-swap drain attack. + // + // Attack vector: + // 1. Attacker controls both user and node keys (two different addresses) + // OR either party signs an invalid state by mistake. + // 2. They create a channel and deposit a worthless token, inflating lockedFunds. + // 3. They sign a withdrawal state that names a valuable token held by the hub. + // 4. Without the token validation, _applyTransitionEffects would read the + // token from the *candidate* state and push valuable tokens to the attacker. + function test_revert_tokenSwap_withdrawDrain() public { + // Deploy a worthless token and give alice a balance + MockERC20 worthless = new MockERC20("Worthless", "WRTH", 18); + worthless.mint(alice, INITIAL_BALANCE); + vm.prank(alice); + worthless.approve(address(cHub), INITIAL_BALANCE); + + ChannelDefinition memory def = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bytes32 channelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + + // Step 1: create channel by depositing worthless tokens + State memory state = State({ + version: 0, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(worthless), + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, state); + + // Verify worthless tokens were deposited and the hub holds them + assertEq(worthless.balanceOf(address(cHub)), DEPOSIT_AMOUNT, "Hub should hold worthless tokens"); + // The hub also holds node's valuable tokens from setUp + assertEq(token.balanceOf(address(cHub)), INITIAL_BALANCE, "Hub should hold node's valuable tokens"); + + // Step 2: attempt to withdraw using a different (valuable) token + State memory malicious = + TestUtils.nextState(state, StateIntent.WITHDRAW, [uint256(0), uint256(0)], [int256(0), int256(0)]); + malicious.homeLedger.token = address(token); // swap to the valuable token + malicious = mutualSignStateBothWithEcdsaValidator(malicious, channelId, ALICE_PK); + + vm.expectRevert(ChannelEngine.TokenMismatch.selector); + vm.prank(alice); + cHub.withdrawFromChannel(channelId, malicious); + + // Confirm no drain occurred + assertEq(token.balanceOf(alice), INITIAL_BALANCE, "Valuable token balance must be unchanged"); + assertEq(token.balanceOf(address(cHub)), INITIAL_BALANCE, "Hub's valuable token balance must be unchanged"); + } +} diff --git a/contracts/test/ChannelHub_singlechain.lifecycle.t.sol b/contracts/test/ChannelHub_lifecycle/ChannelHub_singlechain.lifecycle.t.sol similarity index 85% rename from contracts/test/ChannelHub_singlechain.lifecycle.t.sol rename to contracts/test/ChannelHub_lifecycle/ChannelHub_singlechain.lifecycle.t.sol index 6738c3fb4..8650310e7 100644 --- a/contracts/test/ChannelHub_singlechain.lifecycle.t.sol +++ b/contracts/test/ChannelHub_lifecycle/ChannelHub_singlechain.lifecycle.t.sol @@ -1,16 +1,17 @@ // 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 { // Approve SessionKeyValidator (ID 1) for user signatures by setting bit 1 + // forge-lint: disable-next-line(incorrect-shift) -- intentional bitmask: 1 << N sets bit N uint256 approvedValidators = 1 << SESSION_KEY_VALIDATOR_ID; // Bit 1 = 1 ChannelDefinition memory def = ChannelDefinition({ @@ -69,7 +70,7 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1000, "User balance after channel creation"); // transfer 42 (allocation decreases by 42, node net flow decreases by 42) - state = nextState(state, StateIntent.OPERATE, [uint256(958), uint256(0)], [int256(1000), int256(-42)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(958), uint256(0)], [int256(1000), int256(-42)]); // NOTE: user registers a Session Key SessionKeyAuthorization memory skAuth = TestUtils.buildAndSignSkAuth(vm, aliceSk1, bytes32(0), ALICE_PK); @@ -84,12 +85,13 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { 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)]); + state = TestUtils.nextState(state, StateIntent.OPERATE, [uint256(982), uint256(0)], [int256(1000), int256(-18)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // invoke a deposit (500) // Expected: user allocation = 1482, user net flow = 1500, node allocation = 0, node net flow = -18 - state = nextState(state, StateIntent.DEPOSIT, [uint256(1482), uint256(0)], [int256(1500), int256(-18)]); + state = + TestUtils.nextState(state, StateIntent.DEPOSIT, [uint256(1482), uint256(0)], [int256(1500), int256(-18)]); // NOTE: both sign with ECDSA validator state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); @@ -101,16 +103,19 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1500, "User balance after first deposit"); // transfer 1 - state = nextState(state, StateIntent.OPERATE, [uint256(1481), uint256(0)], [int256(1500), int256(-19)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1481), uint256(0)], [int256(1500), int256(-19)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // transfer 2 - state = nextState(state, StateIntent.OPERATE, [uint256(1479), uint256(0)], [int256(1500), int256(-21)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1479), uint256(0)], [int256(1500), int256(-21)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // invoke a withdrawal (100) // Expected: user allocation = 1379, user net flow = 1400, node allocation = 0, node net flow = -21 - state = nextState(state, StateIntent.WITHDRAW, [uint256(1379), uint256(0)], [int256(1400), int256(-21)]); + state = + TestUtils.nextState(state, StateIntent.WITHDRAW, [uint256(1379), uint256(0)], [int256(1400), int256(-21)]); // NOTE: user signs with Session key validator state = mutualSignStateUserWithSkValidator(state, channelId, ALICE_SK1_PK, skAuth); @@ -122,16 +127,19 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1400, "User balance after first withdrawal"); // transfer 3 - state = nextState(state, StateIntent.OPERATE, [uint256(1376), uint256(0)], [int256(1400), int256(-24)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1376), uint256(0)], [int256(1400), int256(-24)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 10 - state = nextState(state, StateIntent.OPERATE, [uint256(1386), uint256(0)], [int256(1400), int256(-14)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1386), uint256(0)], [int256(1400), int256(-14)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // invoke a deposit (200) // Expected: user allocation = 1586, user net flow = 1600, node allocation = 0, node net flow = -14 - state = nextState(state, StateIntent.DEPOSIT, [uint256(1586), uint256(0)], [int256(1600), int256(-14)]); + state = + TestUtils.nextState(state, StateIntent.DEPOSIT, [uint256(1586), uint256(0)], [int256(1600), int256(-14)]); // NOTE: user signs with Session key validator state = mutualSignStateUserWithSkValidator(state, channelId, ALICE_SK1_PK, skAuth); @@ -143,28 +151,34 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1600, "User balance after second deposit"); // receive 1 - state = nextState(state, StateIntent.OPERATE, [uint256(1587), uint256(0)], [int256(1600), int256(-13)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1587), uint256(0)], [int256(1600), int256(-13)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // transfer 2 - state = nextState(state, StateIntent.OPERATE, [uint256(1585), uint256(0)], [int256(1600), int256(-15)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1585), uint256(0)], [int256(1600), int256(-15)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 3 - state = nextState(state, StateIntent.OPERATE, [uint256(1588), uint256(0)], [int256(1600), int256(-12)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1588), uint256(0)], [int256(1600), int256(-12)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // transfer 4 - state = nextState(state, StateIntent.OPERATE, [uint256(1584), uint256(0)], [int256(1600), int256(-16)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1584), uint256(0)], [int256(1600), int256(-16)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 5 - state = nextState(state, StateIntent.OPERATE, [uint256(1589), uint256(0)], [int256(1600), int256(-11)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1589), uint256(0)], [int256(1600), int256(-11)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // withdraw (300) // Expected: user allocation = 1289, user net flow = 1300, node allocation = 0, node net flow = -11 - state = nextState(state, StateIntent.WITHDRAW, [uint256(1289), uint256(0)], [int256(1300), int256(-11)]); + state = + TestUtils.nextState(state, StateIntent.WITHDRAW, [uint256(1289), uint256(0)], [int256(1300), int256(-11)]); // NOTE: user signs with ECDSA validator state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); @@ -179,22 +193,25 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { // transfer 1 // Expected: user allocation = 1288, user net flow = 1300, node allocation = 0, node net flow = -12 - state = nextState(state, StateIntent.OPERATE, [uint256(1288), uint256(0)], [int256(1300), int256(-12)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1288), uint256(0)], [int256(1300), int256(-12)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // receive 2 // Expected: user allocation = 1290, user net flow = 1300, node allocation = 0, node net flow = -10 - state = nextState(state, StateIntent.OPERATE, [uint256(1290), uint256(0)], [int256(1300), int256(-10)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1290), uint256(0)], [int256(1300), int256(-10)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // transfer 3 // Expected: user allocation = 1287, user net flow = 1300, node allocation = 0, node net flow = -13 - state = nextState(state, StateIntent.OPERATE, [uint256(1287), uint256(0)], [int256(1300), int256(-13)]); + state = + TestUtils.nextState(state, StateIntent.OPERATE, [uint256(1287), uint256(0)], [int256(1300), int256(-13)]); state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); // close channel // Expected: allocations = 0, user net flow 13, node net flow -13 - state = nextState(state, StateIntent.CLOSE, [uint256(0), uint256(0)], [int256(13), int256(-13)]); + state = TestUtils.nextState(state, StateIntent.CLOSE, [uint256(0), uint256(0)], [int256(13), int256(-13)]); // NOTE: user signs with Channel validator state = mutualSignStateUserWithSkValidator(state, channelId, ALICE_SK1_PK, skAuth); diff --git a/contracts/test/ChannelHub_nonRevertingPushFunds.t.sol b/contracts/test/ChannelHub_nonRevertingPushFunds.t.sol new file mode 100644 index 000000000..3d8296c4b --- /dev/null +++ b/contracts/test/ChannelHub_nonRevertingPushFunds.t.sol @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; + +import {TestChannelHub} from "./TestChannelHub.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; +import {NonReturningERC20} from "./mocks/NonReturningERC20.sol"; +import {RevertingERC20} from "./mocks/RevertingERC20.sol"; +import {GasConsumingERC20} from "./mocks/GasConsumingERC20.sol"; +import {MalformedReturningERC20} from "./mocks/MalformedReturningERC20.sol"; +import {DonatingERC20} from "./mocks/DonatingERC20.sol"; +import {OversizedReturnERC20} from "./mocks/OversizedReturnERC20.sol"; +import {RevertingEthReceiver} from "./mocks/RevertingEthReceiver.sol"; +import {GasConsumingEthReceiver} from "./mocks/GasConsumingEthReceiver.sol"; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {ChannelHub} from "../src/ChannelHub.sol"; +import {ECDSAValidator} from "../src/sigValidators/ECDSAValidator.sol"; + +/** + * @notice Simple contract that can receive ETH for testing normal transfers + */ +contract SimpleReceiver { + receive() external payable {} +} + +contract ChannelHubTest_nonRevertingPushFunds is Test { + TestChannelHub public cHub; + MockERC20 public normalToken; + NonReturningERC20 public nonReturningToken; + RevertingERC20 public revertingToken; + GasConsumingERC20 public gasConsumingToken; + MalformedReturningERC20 public malformedToken; + + SimpleReceiver public simpleReceiver; + RevertingEthReceiver public revertingReceiver; + GasConsumingEthReceiver public gasConsumingReceiver; + + address public recipient; + + uint256 constant TRANSFER_AMOUNT = 1000 ether; + uint256 constant BALANCE_AMOUNT = TRANSFER_AMOUNT * 10; + + function setUp() public { + cHub = new TestChannelHub(new ECDSAValidator(), makeAddr("node")); + normalToken = new MockERC20("Normal Token", "NRM", 18); + nonReturningToken = new NonReturningERC20(); + revertingToken = new RevertingERC20(); + gasConsumingToken = new GasConsumingERC20(); + malformedToken = new MalformedReturningERC20(); + + simpleReceiver = new SimpleReceiver(); + revertingReceiver = new RevertingEthReceiver(); + gasConsumingReceiver = new GasConsumingEthReceiver(); + + recipient = makeAddr("recipient"); + + vm.deal(address(cHub), BALANCE_AMOUNT); + + normalToken.mint(address(cHub), BALANCE_AMOUNT); + nonReturningToken.mint(address(cHub), BALANCE_AMOUNT); + revertingToken.mint(address(cHub), BALANCE_AMOUNT); + gasConsumingToken.mint(address(cHub), BALANCE_AMOUNT); + malformedToken.mint(address(cHub), BALANCE_AMOUNT); + } + + function _verifyTransferSuccess(address user, address tokenAddr, uint256 transferredAmount) internal view { + if (tokenAddr == address(0)) { + uint256 userBalanceAfter = user.balance; + assertEq(userBalanceAfter, transferredAmount, "Transfer amount mismatch"); + uint256 channelHubBalanceAfter = address(cHub).balance; + assertEq(channelHubBalanceAfter, BALANCE_AMOUNT - transferredAmount, "ChannelHub balance mismatch"); + } else { + IERC20 token = IERC20(tokenAddr); + uint256 userBalanceAfter = token.balanceOf(user); + assertEq(userBalanceAfter, transferredAmount, "Transfer amount mismatch"); + uint256 channelHubBalanceAfter = token.balanceOf(address(cHub)); + assertEq(channelHubBalanceAfter, BALANCE_AMOUNT - transferredAmount); + } + + assertEq(cHub.getReclaimBalance(user, tokenAddr), 0, "Reclaim amount should be zero"); + } + + function _verifyBalancesNotChanged(address user, address tokenAddr, uint256 expectedReclaimAmount) internal view { + if (tokenAddr == address(0)) { + uint256 userBalanceAfter = user.balance; + assertEq(userBalanceAfter, 0, "User balance should not change"); + uint256 channelHubBalanceAfter = address(cHub).balance; + assertEq(channelHubBalanceAfter, BALANCE_AMOUNT, "ChannelHub balance should not change"); + } else { + IERC20 token = IERC20(tokenAddr); + uint256 userBalanceAfter = token.balanceOf(user); + assertEq(userBalanceAfter, 0, "User balance should not change"); + uint256 channelHubBalanceAfter = token.balanceOf(address(cHub)); + assertEq(channelHubBalanceAfter, BALANCE_AMOUNT, "ChannelHub balance should not change"); + } + + assertEq(cHub.getReclaimBalance(user, tokenAddr), expectedReclaimAmount, "Reclaim amount mismatch"); + } + + // ========== Normal ERC20 Tests ========== + + function test_succeeds_withNormalERC20() public { + cHub.exposed_nonRevertingPushFunds(recipient, address(normalToken), TRANSFER_AMOUNT); + _verifyTransferSuccess(recipient, address(normalToken), TRANSFER_AMOUNT); + } + + function test_succeeds_withZeroAmount() public { + // Should not revert, should be a no-op + cHub.exposed_nonRevertingPushFunds(recipient, address(normalToken), 0); + _verifyBalancesNotChanged(recipient, address(normalToken), 0); + } + + // ========== Non-Returning ERC20 Tests ========== + + function test_succeeds_withNonReturningERC20() public { + cHub.exposed_nonRevertingPushFunds(recipient, address(nonReturningToken), TRANSFER_AMOUNT); + _verifyTransferSuccess(recipient, address(nonReturningToken), TRANSFER_AMOUNT); + } + + // ========== False-Returning ERC20 Tests ========== + + function test_accumulatesReclaims_whenERC20ReturnsFalse() public { + normalToken.setFailTransfers(true); + + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(recipient, address(normalToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(normalToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(normalToken), TRANSFER_AMOUNT); + } + + // ========== Reverting ERC20 Tests ========== + + function test_accumulatesReclaims_whenERC20Reverts() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(recipient, address(revertingToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(revertingToken), TRANSFER_AMOUNT); + } + + function test_accumulatesReclaims_multipleFailedTransfers() public { + cHub.exposed_nonRevertingPushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(revertingToken), TRANSFER_AMOUNT * 2); + } + + // ========== Gas Consuming ERC20 Tests ========== + + function test_accumulatesReclaims_whenERC20ConsumesAllGas() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); + } + + // ========== Malformed Returning ERC20 Tests ========== + + function test_accumulatesReclaims_whenERC20ReturnsMalformedData() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(recipient, address(malformedToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(malformedToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(malformedToken), TRANSFER_AMOUNT); + } + + // ========== Oversized Return Data ERC20 Tests ========== + + // Regression pin for the SC-L01 audit finding. + // The existing oversized-data tests verify behavioural correctness but still pass if the old + // high-level `(bool, bytes memory) = addr.call(...)` form is restored, because Foundry's default + // gas budget is far too large to trigger OOG. This test calls with a tight external gas cap so + // that the old copy path runs out of gas while the assembly fix passes comfortably. + // + // Budget breakdown (Berlin EVM, 80 000 gas forwarded to exposed_nonRevertingPushFunds): + // function overhead + nonReentrant SSTORE + abi.encodeCall : ~11 000 gas + // 63/64 rule → forwarded to token : 67 922 gas + // token execution (100 KB memory expansion, no SSTORE) : ~28 500 gas (returned: 39 422) + // caller gas after CALL (kept 1/64 + returned) : ~40 500 gas + // ── new code post-call (cold reclaim SSTORE + emit) : ~26 250 → 14 250 spare → PASSES + // ── old code extra (memory expand + RETURNDATACOPY 100 KB) : ~38 072 → needs 64 322 → OOGs + function test_doesNotOOG_withTightGas_whenERC20ReturnsLargeData() public { + OversizedReturnERC20 largeToken = new OversizedReturnERC20(100_000, 0); + largeToken.mint(address(cHub), BALANCE_AMOUNT); + + (bool ok,) = address(cHub).call{gas: 80_000}( + abi.encodeCall( + TestChannelHub.exposed_nonRevertingPushFunds, (recipient, address(largeToken), TRANSFER_AMOUNT) + ) + ); + + assertTrue(ok, "assembly path must not OOG: old high-level copy costs ~38k extra gas"); + assertEq(cHub.getReclaimBalance(recipient, address(largeToken)), TRANSFER_AMOUNT, "reclaim must be written"); + } + + // Covers the rdsize > 32, retval != 0 branch of _trySafeTransfer. + // With the old high-level (bool, bytes memory) call form, a token returning a large buffer + // would exhaust caller gas during returndata copy before we could inspect the length. + // The assembly fix caps the copy to 32 bytes, so the first word is read and the transfer succeeds. + function test_succeeds_whenERC20ReturnsOversizedDataWithNonzeroValue() public { + OversizedReturnERC20 oversizedToken = new OversizedReturnERC20(10_240, 1); + oversizedToken.mint(address(cHub), BALANCE_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(oversizedToken), TRANSFER_AMOUNT); + + _verifyTransferSuccess(recipient, address(oversizedToken), TRANSFER_AMOUNT); + } + + // Covers the rdsize > 32, retval == 0 branch of _trySafeTransfer. + // Oversized returndata with a zero first word must be treated as failure and accumulate reclaims, + // not revert. + function test_accumulatesReclaims_whenERC20ReturnsOversizedDataWithZeroValue() public { + OversizedReturnERC20 oversizedToken = new OversizedReturnERC20(10_240, 0); + oversizedToken.mint(address(cHub), BALANCE_AMOUNT); + + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(recipient, address(oversizedToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(oversizedToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(oversizedToken), TRANSFER_AMOUNT); + } + + // Covers the rdsize ∈ [1, 31] branch of _trySafeTransfer. + // Any response shorter than 32 bytes is rejected as failure regardless of content — + // a valid ERC20 bool is always a full 32-byte ABI word. + function test_accumulatesReclaims_whenERC20ReturnsShortData() public { + OversizedReturnERC20 shortToken = new OversizedReturnERC20(16, 0); + shortToken.mint(address(cHub), BALANCE_AMOUNT); + + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(recipient, address(shortToken), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(shortToken), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(recipient, address(shortToken), TRANSFER_AMOUNT); + } + + // Covers rdsize == 32 with a non-canonical bool value (2). + // abi.decode(..., (bool)) reverts on value 2 — Solidity 0.8 only accepts 0 or 1. + // Decoding as uint256 and checking != 0 handles this correctly without reverting. + function test_succeeds_whenERC20ReturnsNonCanonicalBoolValue() public { + OversizedReturnERC20 nonCanonicalToken = new OversizedReturnERC20(32, 2); + nonCanonicalToken.mint(address(cHub), BALANCE_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(nonCanonicalToken), TRANSFER_AMOUNT); + + _verifyTransferSuccess(recipient, address(nonCanonicalToken), TRANSFER_AMOUNT); + } + + // ========== ERC777 Donation-Back Tests ========== + + function test_succeeds_whenERC777DonatesBack() public { + uint256 donationAmount = 1 ether; + DonatingERC20 donatingToken = new DonatingERC20(address(cHub), donationAmount); + donatingToken.mint(address(cHub), BALANCE_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(recipient, address(donatingToken), TRANSFER_AMOUNT); + + // Recipient received the transferred amount + assertEq(donatingToken.balanceOf(recipient), TRANSFER_AMOUNT, "Recipient should have received tokens"); + // No false reclaim: old balance-check code would have incorrectly added TRANSFER_AMOUNT here + assertEq(cHub.getReclaimBalance(recipient, address(donatingToken)), 0, "No reclaim should be created"); + } + + // ========== Native ETH Tests ========== + + function test_succeeds_withNativeETH() public { + cHub.exposed_nonRevertingPushFunds(recipient, address(0), TRANSFER_AMOUNT); + + _verifyTransferSuccess(recipient, address(0), TRANSFER_AMOUNT); + } + + function test_succeeds_withNativeETH_toContract() public { + cHub.exposed_nonRevertingPushFunds(address(simpleReceiver), address(0), TRANSFER_AMOUNT); + + _verifyTransferSuccess(address(simpleReceiver), address(0), TRANSFER_AMOUNT); + } + + // ========== Reverting ETH Receiver Tests ========== + + function test_accumulatesReclaims_whenETHReceiverReverts() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(address(revertingReceiver), address(0), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(address(revertingReceiver), address(0), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(address(revertingReceiver), address(0), TRANSFER_AMOUNT); + } + + // ========== Gas Consuming ETH Receiver Tests ========== + + function test_accumulatesReclaims_whenETHReceiverConsumesAllGas() public { + vm.expectEmit(true, true, false, true); + emit ChannelHub.TransferFailed(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); + + cHub.exposed_nonRevertingPushFunds(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); + + _verifyBalancesNotChanged(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); + } +} diff --git a/contracts/test/ChannelHub_pushFunds.t.sol b/contracts/test/ChannelHub_pushFunds.t.sol deleted file mode 100644 index 8c0f8fac3..000000000 --- a/contracts/test/ChannelHub_pushFunds.t.sol +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.30; - -import {Test} from "forge-std/Test.sol"; - -import {TestChannelHub} from "./TestChannelHub.sol"; -import {MockERC20} from "./mocks/MockERC20.sol"; -import {NonReturningERC20} from "./mocks/NonReturningERC20.sol"; -import {RevertingERC20} from "./mocks/RevertingERC20.sol"; -import {GasConsumingERC20} from "./mocks/GasConsumingERC20.sol"; -import {MalformedReturningERC20} from "./mocks/MalformedReturningERC20.sol"; -import {RevertingEthReceiver} from "./mocks/RevertingEthReceiver.sol"; -import {GasConsumingEthReceiver} from "./mocks/GasConsumingEthReceiver.sol"; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {ChannelHub} from "../src/ChannelHub.sol"; -import {ECDSAValidator} from "../src/sigValidators/ECDSAValidator.sol"; - -/** - * @notice Simple contract that can receive ETH for testing normal transfers - */ -contract SimpleReceiver { - receive() external payable {} -} - -contract ChannelHubTest_pushFunds is Test { - TestChannelHub public cHub; - MockERC20 public normalToken; - NonReturningERC20 public nonReturningToken; - RevertingERC20 public revertingToken; - GasConsumingERC20 public gasConsumingToken; - MalformedReturningERC20 public malformedToken; - - SimpleReceiver public simpleReceiver; - RevertingEthReceiver public revertingReceiver; - GasConsumingEthReceiver public gasConsumingReceiver; - - address public recipient; - - uint256 constant TRANSFER_AMOUNT = 1000 ether; - uint256 constant BALANCE_AMOUNT = TRANSFER_AMOUNT * 10; - - function setUp() public { - cHub = new TestChannelHub(new ECDSAValidator()); - normalToken = new MockERC20("Normal Token", "NRM", 18); - nonReturningToken = new NonReturningERC20(); - revertingToken = new RevertingERC20(); - gasConsumingToken = new GasConsumingERC20(); - malformedToken = new MalformedReturningERC20(); - - simpleReceiver = new SimpleReceiver(); - revertingReceiver = new RevertingEthReceiver(); - gasConsumingReceiver = new GasConsumingEthReceiver(); - - recipient = makeAddr("recipient"); - - vm.deal(address(cHub), BALANCE_AMOUNT); - - normalToken.mint(address(cHub), BALANCE_AMOUNT); - nonReturningToken.mint(address(cHub), BALANCE_AMOUNT); - revertingToken.mint(address(cHub), BALANCE_AMOUNT); - gasConsumingToken.mint(address(cHub), BALANCE_AMOUNT); - malformedToken.mint(address(cHub), BALANCE_AMOUNT); - } - - function _verifyTransferSuccess(address user, address tokenAddr, uint256 transferredAmount) internal view { - if (tokenAddr == address(0)) { - uint256 userBalanceAfter = user.balance; - assertEq(userBalanceAfter, transferredAmount, "Transfer amount mismatch"); - uint256 channelHubBalanceAfter = address(cHub).balance; - assertEq(channelHubBalanceAfter, BALANCE_AMOUNT - transferredAmount, "ChannelHub balance mismatch"); - } else { - IERC20 token = IERC20(tokenAddr); - uint256 userBalanceAfter = token.balanceOf(user); - assertEq(userBalanceAfter, transferredAmount, "Transfer amount mismatch"); - uint256 channelHubBalanceAfter = token.balanceOf(address(cHub)); - assertEq(channelHubBalanceAfter, BALANCE_AMOUNT - transferredAmount); - } - - assertEq(cHub.getReclaimBalance(user, tokenAddr), 0, "Reclaim amount should be zero"); - } - - function _verifyBalancesNotChanged(address user, address tokenAddr, uint256 expectedReclaimAmount) internal view { - if (tokenAddr == address(0)) { - uint256 userBalanceAfter = user.balance; - assertEq(userBalanceAfter, 0, "User balance should not change"); - uint256 channelHubBalanceAfter = address(cHub).balance; - assertEq(channelHubBalanceAfter, BALANCE_AMOUNT, "ChannelHub balance should not change"); - } else { - IERC20 token = IERC20(tokenAddr); - uint256 userBalanceAfter = token.balanceOf(user); - assertEq(userBalanceAfter, 0, "User balance should not change"); - uint256 channelHubBalanceAfter = token.balanceOf(address(cHub)); - assertEq(channelHubBalanceAfter, BALANCE_AMOUNT, "ChannelHub balance should not change"); - } - - assertEq(cHub.getReclaimBalance(user, tokenAddr), expectedReclaimAmount, "Reclaim amount mismatch"); - } - - // ========== Normal ERC20 Tests ========== - - function test_succeeds_withNormalERC20() public { - cHub.exposed_pushFunds(recipient, address(normalToken), TRANSFER_AMOUNT); - _verifyTransferSuccess(recipient, address(normalToken), TRANSFER_AMOUNT); - } - - function test_succeeds_withZeroAmount() public { - // Should not revert, should be a no-op - cHub.exposed_pushFunds(recipient, address(normalToken), 0); - _verifyBalancesNotChanged(recipient, address(normalToken), 0); - } - - // ========== Non-Returning ERC20 Tests ========== - - function test_succeeds_withNonReturningERC20() public { - cHub.exposed_pushFunds(recipient, address(nonReturningToken), TRANSFER_AMOUNT); - _verifyTransferSuccess(recipient, address(nonReturningToken), TRANSFER_AMOUNT); - } - - // ========== Reverting ERC20 Tests ========== - - function test_accumulatesReclaims_whenERC20Reverts() public { - vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(recipient, address(revertingToken), TRANSFER_AMOUNT); - - cHub.exposed_pushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); - - _verifyBalancesNotChanged(recipient, address(revertingToken), TRANSFER_AMOUNT); - } - - function test_accumulatesReclaims_multipleFailedTransfers() public { - cHub.exposed_pushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); - - cHub.exposed_pushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); - - _verifyBalancesNotChanged(recipient, address(revertingToken), TRANSFER_AMOUNT * 2); - } - - // ========== Gas Consuming ERC20 Tests ========== - - function test_accumulatesReclaims_whenERC20ConsumesAllGas() public { - vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); - - cHub.exposed_pushFunds(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); - - _verifyBalancesNotChanged(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); - } - - // ========== Malformed Returning ERC20 Tests ========== - - function test_accumulatesReclaims_whenERC20ReturnsMalformedData() public { - vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(recipient, address(malformedToken), TRANSFER_AMOUNT); - - cHub.exposed_pushFunds(recipient, address(malformedToken), TRANSFER_AMOUNT); - - _verifyBalancesNotChanged(recipient, address(malformedToken), TRANSFER_AMOUNT); - } - - // ========== Native ETH Tests ========== - - function test_succeeds_withNativeETH() public { - cHub.exposed_pushFunds(recipient, address(0), TRANSFER_AMOUNT); - - _verifyTransferSuccess(recipient, address(0), TRANSFER_AMOUNT); - } - - function test_succeeds_withNativeETH_toContract() public { - cHub.exposed_pushFunds(address(simpleReceiver), address(0), TRANSFER_AMOUNT); - - _verifyTransferSuccess(address(simpleReceiver), address(0), TRANSFER_AMOUNT); - } - - // ========== Reverting ETH Receiver Tests ========== - - function test_accumulatesReclaims_whenETHReceiverReverts() public { - vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(address(revertingReceiver), address(0), TRANSFER_AMOUNT); - - cHub.exposed_pushFunds(address(revertingReceiver), address(0), TRANSFER_AMOUNT); - - _verifyBalancesNotChanged(address(revertingReceiver), address(0), TRANSFER_AMOUNT); - } - - // ========== Gas Consuming ETH Receiver Tests ========== - - function test_accumulatesReclaims_whenETHReceiverConsumesAllGas() public { - vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); - - cHub.exposed_pushFunds(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); - - _verifyBalancesNotChanged(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); - } -} diff --git a/contracts/test/ChannelHub_sigValidator.t.sol b/contracts/test/ChannelHub_sigValidator.t.sol new file mode 100644 index 000000000..389c49106 --- /dev/null +++ b/contracts/test/ChannelHub_sigValidator.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; + +import {TestChannelHub} from "./TestChannelHub.sol"; +import {TestUtils} from "./TestUtils.sol"; + +import {ChannelHub} from "../src/ChannelHub.sol"; +import {ECDSAValidator} from "../src/sigValidators/ECDSAValidator.sol"; +import {SessionKeyValidator} from "../src/sigValidators/SessionKeyValidator.sol"; +import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; +import {DEFAULT_SIG_VALIDATOR_ID} from "../src/interfaces/Types.sol"; + +abstract contract ChannelHubTest_SigValidator_Base is Test { + TestChannelHub public cHub; + + uint256 constant NODE_PK = 1; + address node; + + ISignatureValidator immutable ECDSA_VALIDATOR = new ECDSAValidator(); + ISignatureValidator immutable SK_VALIDATOR = new SessionKeyValidator(); + + uint8 constant VALIDATOR_ID = 1; + + function setUp() public virtual { + node = vm.addr(NODE_PK); + cHub = new TestChannelHub(ECDSA_VALIDATOR, node); + } + + // -------- helpers -------- + + function _registerValidator(uint8 validatorId, ISignatureValidator validator) internal { + bytes memory sig = + TestUtils.buildAndSignValidatorRegistration(vm, validatorId, address(validator), NODE_PK, address(cHub)); + cHub.registerNodeValidator(validatorId, validator, sig); + } + + function _registerAndActivate(uint8 validatorId, ISignatureValidator validator) internal { + _registerValidator(validatorId, validator); + vm.warp(block.timestamp + cHub.VALIDATOR_ACTIVATION_DELAY() + 1); + } + + /// @dev Build a 1-byte-prefixed signature with the given validatorId and dummy sig data. + function _sig(uint8 validatorId) internal pure returns (bytes memory) { + return abi.encodePacked(validatorId, bytes32(0), bytes32(0), uint8(27)); + } +} + +contract ChannelHubTest_RegisterNodeValidator is ChannelHubTest_SigValidator_Base { + function test_success_storesValidatorInfo() public { + uint256 ts = 1_000_000; + vm.warp(ts); + + _registerValidator(VALIDATOR_ID, SK_VALIDATOR); + + (ISignatureValidator stored, uint64 registeredAt) = cHub.getNodeValidator(VALIDATOR_ID); + assertEq(address(stored), address(SK_VALIDATOR)); + assertEq(registeredAt, ts); + } + + function test_success_emitsValidatorRegistered() public { + bytes memory sig = TestUtils.buildAndSignValidatorRegistration( + vm, VALIDATOR_ID, address(SK_VALIDATOR), NODE_PK, address(cHub) + ); + + vm.expectEmit(true, true, true, true); + emit ChannelHub.ValidatorRegistered(VALIDATOR_ID, SK_VALIDATOR); + + cHub.registerNodeValidator(VALIDATOR_ID, SK_VALIDATOR, sig); + } + + function test_revert_defaultValidatorId() public { + bytes memory sig = TestUtils.buildAndSignValidatorRegistration( + vm, DEFAULT_SIG_VALIDATOR_ID, address(SK_VALIDATOR), NODE_PK, address(cHub) + ); + + vm.expectRevert(ChannelHub.InvalidValidatorId.selector); + cHub.registerNodeValidator(DEFAULT_SIG_VALIDATOR_ID, SK_VALIDATOR, sig); + } + + function test_revert_zeroValidatorAddress() public { + bytes memory sig = + TestUtils.buildAndSignValidatorRegistration(vm, VALIDATOR_ID, address(0), NODE_PK, address(cHub)); + + vm.expectRevert(ChannelHub.InvalidAddress.selector); + cHub.registerNodeValidator(VALIDATOR_ID, ISignatureValidator(address(0)), sig); + } + + function test_revert_duplicateValidatorId() public { + _registerValidator(VALIDATOR_ID, SK_VALIDATOR); + + bytes memory sig2 = TestUtils.buildAndSignValidatorRegistration( + vm, VALIDATOR_ID, address(SK_VALIDATOR), NODE_PK, address(cHub) + ); + + vm.expectRevert(abi.encodeWithSelector(ChannelHub.ValidatorAlreadyRegistered.selector, VALIDATOR_ID)); + cHub.registerNodeValidator(VALIDATOR_ID, SK_VALIDATOR, sig2); + } +} + +contract ChannelHubTest_ExtractValidator is ChannelHubTest_SigValidator_Base { + /// @dev approvedSignatureValidators bitmask with bit `id` set. + function _approved(uint8 id) internal pure returns (uint256) { + // forge-lint: disable-next-line(incorrect-shift) -- intentional bitmask: 1 << N sets bit N + return 1 << id; + } + + function test_success_defaultValidator() public view { + bytes memory sig = _sig(DEFAULT_SIG_VALIDATOR_ID); + + ISignatureValidator result = cHub.exposed_extractValidator(sig, 0); + + assertEq(address(result), address(cHub.DEFAULT_SIG_VALIDATOR())); + } + + function test_success_activeNodeValidator() public { + _registerAndActivate(VALIDATOR_ID, SK_VALIDATOR); + + bytes memory sig = _sig(VALIDATOR_ID); + + ISignatureValidator result = cHub.exposed_extractValidator(sig, _approved(VALIDATOR_ID)); + + assertEq(address(result), address(SK_VALIDATOR)); + } + + function test_revert_validatorNotRegistered() public { + bytes memory sig = _sig(VALIDATOR_ID); + + vm.expectRevert(abi.encodeWithSelector(ChannelHub.ValidatorNotRegistered.selector, VALIDATOR_ID)); + cHub.exposed_extractValidator(sig, _approved(VALIDATOR_ID)); + } + + function test_revert_validatorNotYetActive() public { + _registerValidator(VALIDATOR_ID, SK_VALIDATOR); + + // Advance time but stay just inside the delay + vm.warp(block.timestamp + cHub.VALIDATOR_ACTIVATION_DELAY() - 1); + + bytes memory sig = _sig(VALIDATOR_ID); + + uint64 expectedActivatesAt = uint64(block.timestamp + 1); + vm.expectRevert( + abi.encodeWithSelector(ChannelHub.ValidatorNotActive.selector, VALIDATOR_ID, expectedActivatesAt) + ); + cHub.exposed_extractValidator(sig, _approved(VALIDATOR_ID)); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_challenge.t.sol b/contracts/test/ChannelHub_units/ChannelHub_challenge.t.sol new file mode 100644 index 000000000..f6f43b3d1 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_challenge.t.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; +import {TestUtils} from "../TestUtils.sol"; +import {Utils} from "../../src/Utils.sol"; +import {ChannelHub} from "../../src/ChannelHub.sol"; +import { + State, + ChannelDefinition, + StateIntent, + Ledger, + ChannelStatus, + ParticipantIndex +} from "../../src/interfaces/Types.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelHubTest_challenge is ChannelHubTest_Base { + ChannelDefinition internal def; + bytes32 internal channelId; + State internal initState; + State internal escrowState; + + uint256 constant ESCROW_AMOUNT = 500; + uint64 constant NON_HOME_CHAIN_ID = 42; + address constant NON_HOME_TOKEN = address(42); + + 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); + + initState = 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, initState); + + // Build INITIATE_ESCROW_DEPOSIT: node locks ESCROW_AMOUNT on home chain, + // user will lock ESCROW_AMOUNT on non-home chain. + escrowState = TestUtils.nextState( + initState, + StateIntent.INITIATE_ESCROW_DEPOSIT, + [uint256(DEPOSIT_AMOUNT), uint256(ESCROW_AMOUNT)], + [int256(DEPOSIT_AMOUNT), int256(ESCROW_AMOUNT)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(ESCROW_AMOUNT), uint256(0)], + [int256(ESCROW_AMOUNT), int256(0)] + ); + escrowState = mutualSignStateBothWithEcdsaValidator(escrowState, channelId, ALICE_PK); + } + + // ========== StateIntent ========== + + function test_revert_closeIntent() public { + State memory state; + state.version = 1; + state.intent = StateIntent.CLOSE; + + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, state, ALICE_PK); + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + vm.prank(alice); + cHub.challengeChannel(channelId, state, challengerSig, ParticipantIndex.USER); + } + + function test_revert_finalizeMigrationIntent() public { + State memory state; + state.version = 1; + state.intent = StateIntent.FINALIZE_MIGRATION; + + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, state, ALICE_PK); + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + vm.prank(alice); + cHub.challengeChannel(channelId, state, challengerSig, ParticipantIndex.USER); + } + + // ========== Payable ========== + + function test_revert_ifETHSent_sameVersionChallenge() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initState, NODE_PK); + + vm.deal(node, 1); + vm.expectRevert(ChannelHub.IncorrectValue.selector); + vm.prank(node); + cHub.challengeChannel{value: 1}(channelId, initState, challengerSig, ParticipantIndex.NODE); + } + + function test_revert_challengeChannel_initiateEscrowDepositIntent_ifETHSent() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, escrowState, NODE_PK); + + vm.deal(node, 1); + vm.expectRevert(ChannelHub.IncorrectValue.selector); + vm.prank(node); + cHub.challengeChannel{value: 1}(channelId, escrowState, challengerSig, ParticipantIndex.NODE); + } + + function test_nativeDepositChallenge_acceptsExactETH() public { + uint256 depositDelta = 100; + ChannelDefinition memory nativeDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 1, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bytes32 nativeChannelId = Utils.getChannelId(nativeDef, CHANNEL_HUB_VERSION); + + State memory nativeInitState = State({ + version: 0, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(0), + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + nativeInitState = mutualSignStateBothWithEcdsaValidator(nativeInitState, nativeChannelId, ALICE_PK); + + vm.deal(alice, DEPOSIT_AMOUNT); + vm.prank(alice); + cHub.createChannel{value: DEPOSIT_AMOUNT}(nativeDef, nativeInitState); + + State memory depositState = TestUtils.nextState( + nativeInitState, + StateIntent.DEPOSIT, + [uint256(DEPOSIT_AMOUNT + depositDelta), uint256(0)], + [int256(DEPOSIT_AMOUNT + depositDelta), int256(0)] + ); + depositState = mutualSignStateBothWithEcdsaValidator(depositState, nativeChannelId, ALICE_PK); + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(nativeChannelId, depositState, NODE_PK); + + uint256 hubBalanceBefore = address(cHub).balance; + vm.deal(node, depositDelta); + vm.prank(node); + cHub.challengeChannel{value: depositDelta}(nativeChannelId, depositState, challengerSig, ParticipantIndex.NODE); + + (ChannelStatus status,, State memory latestState, uint256 challengeExpiry,) = + cHub.getChannelData(nativeChannelId); + assertEq(uint8(status), uint8(ChannelStatus.DISPUTED), "Channel should be DISPUTED"); + assertEq(latestState.version, 1, "Native deposit state should be enforced"); + assertEq( + latestState.homeLedger.userAllocation, + DEPOSIT_AMOUNT + depositDelta, + "Native allocation should include challenge deposit" + ); + assertEq(challengeExpiry, block.timestamp + CHALLENGE_DURATION, "Challenge expiry should be set"); + assertEq(address(cHub).balance, hubBalanceBefore + depositDelta, "Native ETH should be pulled"); + } + + // ========== INITIATE_ESCROW_DEPOSIT caller restriction ========== + + function test_revert_initiateEscrowDeposit_homeChain_callerNotNode() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, escrowState, ALICE_PK); + + vm.expectRevert(ChannelHub.IncorrectMsgSender.selector); + vm.prank(alice); + cHub.challengeChannel(channelId, escrowState, challengerSig, ParticipantIndex.USER); + } + + function test_initiateEscrowDeposit_homeChain_nodeCanChallenge() public { + uint256 nodeBalanceBefore = cHub.getNodeBalance(address(token)); + + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, escrowState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, escrowState, challengerSig, ParticipantIndex.NODE); + + // State is enforced and channel enters DISPUTED + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + 1, + block.timestamp + CHALLENGE_DURATION, + "Channel should be DISPUTED with escrow state enforced" + ); + assertEq( + cHub.getNodeBalance(address(token)), + nodeBalanceBefore - ESCROW_AMOUNT, + "Node balance should decrease by escrow amount" + ); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_checkpointChannel.t.sol b/contracts/test/ChannelHub_units/ChannelHub_checkpointChannel.t.sol new file mode 100644 index 000000000..f67308609 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_checkpointChannel.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_checkpointChannel is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.checkpointChannel(bytes32(0), state); + } + + // ========== Payable ========== + + // The EVM rejects ETH at the dispatcher level before any Solidity code runs, + // producing an empty revert (no error selector). + + function test_revert_ifETHSent() public { + State memory state; + + vm.deal(address(this), 1); + (bool success, bytes memory returnData) = + address(cHub).call{value: 1}(abi.encodeCall(cHub.checkpointChannel, (bytes32(0), state))); + + assertFalse(success, "checkpointChannel must not accept ETH"); + assertEq(returnData.length, 0, "Non-payable rejection produces no error data"); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_closeChannel.t.sol b/contracts/test/ChannelHub_units/ChannelHub_closeChannel.t.sol new file mode 100644 index 000000000..dd6743eeb --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_closeChannel.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_closeChannel is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.closeChannel(bytes32(0), state); + } + + // ========== Payable ========== + + // The EVM rejects ETH at the dispatcher level before any Solidity code runs, + // producing an empty revert (no error selector). + + function test_revert_ifETHSent() public { + State memory state; + + vm.deal(address(this), 1); + (bool success, bytes memory returnData) = + address(cHub).call{value: 1}(abi.encodeCall(cHub.closeChannel, (bytes32(0), state))); + + assertFalse(success, "closeChannel must not accept ETH"); + assertEq(returnData.length, 0, "Non-payable rejection produces no error data"); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_createChannel.t.sol b/contracts/test/ChannelHub_units/ChannelHub_createChannel.t.sol new file mode 100644 index 000000000..7ae773ac9 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_createChannel.t.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {Utils} from "../../src/Utils.sol"; +import {ChannelDefinition, ChannelStatus, State, StateIntent, Ledger} from "../../src/interfaces/Types.sol"; +import {TestUtils} from "../TestUtils.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelHubTest_createChannel is ChannelHubTest_Base { + ChannelDefinition internal def; + bytes32 internal channelId; + + State initialDepositState; + + 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); + + initialDepositState = 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + } + + // ========== Success: create on VOID channel ========== + + function test_createChannel_depositIntent() public { + State memory state = mutualSignStateBothWithEcdsaValidator(initialDepositState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, state); + + verifyChannelData( + channelId, + ChannelStatus.OPERATING, + 0, + 0, + "Channel status should be OPERATING after createChannel with DEPOSIT intent" + ); + } + + function test_createChannel_withdrawIntent() public { + // Represents an off-chain state (version > 0) where the node funded the user directly. + // userNetFlow < 0: net funds flowed out to user; nodeNetFlow > 0: node vault funds that. + // allocsSum must equal netFlowsSum (= 0), so both allocations are 0. + State memory state = TestUtils.nextState( + initialDepositState, + StateIntent.WITHDRAW, + [uint256(0), 0], + [-int256(DEPOSIT_AMOUNT), int256(DEPOSIT_AMOUNT)] + ); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, state); + + verifyChannelData( + channelId, + ChannelStatus.OPERATING, + 1, + 0, + "Channel status should be OPERATING after createChannel with WITHDRAW intent" + ); + } + + function test_createChannel_operateIntent() public { + // Represents an off-chain state (version > 0) with no on-chain fund movement yet. + State memory state = + TestUtils.nextState(initialDepositState, StateIntent.OPERATE, [uint256(0), 0], [int256(0), 0]); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, state); + + verifyChannelData( + channelId, + ChannelStatus.OPERATING, + 1, + 0, + "Channel status should be OPERATING after createChannel with OPERATE intent" + ); + } + + function test_createChannel_allowsMaxChallengeDuration() public { + def.challengeDuration = cHub.MAX_CHALLENGE_DURATION(); + bytes32 maxDurationChannelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + State memory state = mutualSignStateBothWithEcdsaValidator(initialDepositState, maxDurationChannelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, state); + + verifyChannelData( + maxDurationChannelId, + ChannelStatus.OPERATING, + 0, + 0, + "Channel status should be OPERATING when challenge duration is the contract max" + ); + } + + // ========== Revert: createChannel on existing channel ========== + + function _createDefaultChannel() internal { + State memory state = mutualSignStateBothWithEcdsaValidator(initialDepositState, channelId, ALICE_PK); + vm.prank(alice); + cHub.createChannel(def, state); + } + + function test_revert_ifChannelExists_depositIntent() public { + _createDefaultChannel(); + + State memory state = TestUtils.nextState( + initialDepositState, + StateIntent.DEPOSIT, + [DEPOSIT_AMOUNT, 0], + [-int256(DEPOSIT_AMOUNT), int256(DEPOSIT_AMOUNT)] + ); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + vm.prank(alice); + vm.expectRevert(ChannelHub.IncorrectChannelStatus.selector); + cHub.createChannel(def, state); + } + + function test_revert_ifChannelExists_withdrawIntent() public { + _createDefaultChannel(); + + State memory state = TestUtils.nextState( + initialDepositState, + StateIntent.WITHDRAW, + [DEPOSIT_AMOUNT, 0], + [-int256(DEPOSIT_AMOUNT), int256(DEPOSIT_AMOUNT)] + ); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + vm.prank(alice); + vm.expectRevert(ChannelHub.IncorrectChannelStatus.selector); + cHub.createChannel(def, state); + } + + function test_revert_ifChannelExists_operateIntent() public { + _createDefaultChannel(); + + State memory state = + TestUtils.nextState(initialDepositState, StateIntent.OPERATE, [uint256(0), 0], [int256(0), 0]); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + vm.prank(alice); + vm.expectRevert(ChannelHub.IncorrectChannelStatus.selector); + cHub.createChannel(def, state); + } + + // ========== StateIntent ========== + + function test_revert_ifDisallowedIntent() public { + State memory state; + state.intent = StateIntent.CLOSE; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.createChannel(def, state); + } + + // ========== Challenge duration ========== + + function test_revert_ifChallengeDurationAboveMax() public { + def.challengeDuration = cHub.MAX_CHALLENGE_DURATION() + 1; + bytes32 tooLongChannelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + State memory state = mutualSignStateBothWithEcdsaValidator(initialDepositState, tooLongChannelId, ALICE_PK); + + vm.prank(alice); + vm.expectRevert(ChannelHub.IncorrectChallengeDuration.selector); + cHub.createChannel(def, state); + } + + function test_revert_ifChallengeDurationBelowMin() public { + def.challengeDuration = cHub.MIN_CHALLENGE_DURATION() - 1; + bytes32 tooShortChannelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + State memory state = mutualSignStateBothWithEcdsaValidator(initialDepositState, tooShortChannelId, ALICE_PK); + + vm.prank(alice); + vm.expectRevert(ChannelHub.IncorrectChallengeDuration.selector); + cHub.createChannel(def, state); + } + + // ========== Payable ========== + + // createChannel is payable to support native ETH deposits (DEPOSIT intent). + // For WITHDRAW and OPERATE intents, no funds are pulled from the user, + // so any ETH sent with these intents is explicitly rejected. + + function test_revert_ifETHSent_withdrawIntent() public { + State memory state; + state.intent = StateIntent.WITHDRAW; + + vm.deal(address(this), 1); + vm.expectRevert(ChannelHub.IncorrectValue.selector); + cHub.createChannel{value: 1}(def, state); + } + + function test_revert_ifETHSent_operateIntent() public { + State memory state; + state.intent = StateIntent.OPERATE; + + vm.deal(address(this), 1); + vm.expectRevert(ChannelHub.IncorrectValue.selector); + cHub.createChannel{value: 1}(def, state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_depositToChannel.t.sol b/contracts/test/ChannelHub_units/ChannelHub_depositToChannel.t.sol new file mode 100644 index 000000000..4e03400cc --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_depositToChannel.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_depositToChannel is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.WITHDRAW; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.depositToChannel(bytes32(0), state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_finalizeEscrowDeposit.t.sol b/contracts/test/ChannelHub_units/ChannelHub_finalizeEscrowDeposit.t.sol new file mode 100644 index 000000000..36425e1a0 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_finalizeEscrowDeposit.t.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; +import {TestUtils} from "../TestUtils.sol"; +import {Utils} from "../../src/Utils.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import { + State, + ChannelDefinition, + StateIntent, + Ledger, + EscrowStatus, + ParticipantIndex +} from "../../src/interfaces/Types.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelHubTest_finalizeEscrowDeposit is ChannelHubTest_Base { + ChannelDefinition internal def; + bytes32 internal channelId; + State internal initState; + + uint256 constant ESCROW_AMOUNT = 500; + uint64 constant NON_HOME_CHAIN_ID = 42; + address constant NON_HOME_TOKEN = address(42); + + 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); + + initState = 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, initState); + } + + // ========== StateIntent ========== + + function test_revert_homeChain_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.finalizeEscrowDeposit(channelId, bytes32(0), state); + } + + // ========== Challenge Expiry Clearing ========== + + // Regression test: cooperative finalization from DISPUTED must zero out challengeExpireAt. + // Before the fix, _applyEscrowDepositEffects used `if (effects.newChallengeExpiry > 0)` which + // skipped the write when the finalize effects left newChallengeExpiry at 0, leaving a stale + // non-zero value observable via getEscrowDepositData(). + function test_cooperativeFinalize_fromDISPUTED_clearsChallengeExpiry() public { + ChannelDefinition memory altDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 1, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bytes32 altChannelId = Utils.getChannelId(altDef, CHANNEL_HUB_VERSION); + + // Current chain acts as non-home chain; NON_HOME_CHAIN_ID is the home chain. + State memory escrowInitState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: ESCROW_AMOUNT, + nodeNetFlow: int256(ESCROW_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: ESCROW_AMOUNT, + userNetFlow: int256(ESCROW_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + escrowInitState = mutualSignStateBothWithEcdsaValidator(escrowInitState, altChannelId, ALICE_PK); + + vm.prank(alice); + cHub.initiateEscrowDeposit(altDef, escrowInitState); + bytes32 escrowId = Utils.getEscrowId(altChannelId, escrowInitState.version); + + // Challenge → status becomes DISPUTED, challengeExpireAt set to a future timestamp. + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(altChannelId, escrowInitState, ALICE_PK); + cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.USER); + + (, EscrowStatus disputedStatus,, uint64 challengeExpiryAfterChallenge,,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(disputedStatus), uint8(EscrowStatus.DISPUTED), "Should be DISPUTED after challenge"); + assertGt(challengeExpiryAfterChallenge, 0, "challengeExpireAt should be non-zero after challenge"); + + // Cooperatively finalize before the challenge period expires. + // Home userNetFlow must be unchanged (delta == 0), home userAllocation grows by ESCROW_AMOUNT. + State memory finalizeState = TestUtils.nextState( + escrowInitState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [DEPOSIT_AMOUNT + ESCROW_AMOUNT, uint256(0)], + [int256(DEPOSIT_AMOUNT), int256(ESCROW_AMOUNT)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [int256(ESCROW_AMOUNT), -int256(ESCROW_AMOUNT)] + ); + finalizeState = mutualSignStateBothWithEcdsaValidator(finalizeState, altChannelId, ALICE_PK); + cHub.finalizeEscrowDeposit(altChannelId, escrowId, finalizeState); + + // Assert: status FINALIZED and challengeExpireAt cleared to zero. + (, EscrowStatus finalStatus,, uint64 finalChallengeExpiry,,) = cHub.getEscrowDepositData(escrowId); + assertEq( + uint8(finalStatus), uint8(EscrowStatus.FINALIZED), "Should be FINALIZED after cooperative finalization" + ); + assertEq( + finalChallengeExpiry, 0, "challengeExpireAt must be cleared after cooperative finalization from DISPUTED" + ); + } + + function test_revert_nonHomeChain_ifWrongIntent() public { + // Use a different nonce so this channel does not exist on the current chain (non-home path). + ChannelDefinition memory altDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 1, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bytes32 altChannelId = Utils.getChannelId(altDef, CHANNEL_HUB_VERSION); + + // Current chain acts as non-home chain; NON_HOME_CHAIN_ID is the home chain. + State memory escrowInitState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: ESCROW_AMOUNT, + nodeNetFlow: int256(ESCROW_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: ESCROW_AMOUNT, + userNetFlow: int256(ESCROW_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + escrowInitState = mutualSignStateBothWithEcdsaValidator(escrowInitState, altChannelId, ALICE_PK); + + // Channel is VOID here, so initiateEscrowDeposit takes the non-home path and writes metadata. + cHub.initiateEscrowDeposit(altDef, escrowInitState); + bytes32 escrowId = Utils.getEscrowId(altChannelId, escrowInitState.version); + + // Finalize with wrong intent — must exercise the non-home metadata path and revert. + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.finalizeEscrowDeposit(altChannelId, escrowId, state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_finalizeEscrowWithdrawal.t.sol b/contracts/test/ChannelHub_units/ChannelHub_finalizeEscrowWithdrawal.t.sol new file mode 100644 index 000000000..4d31414cf --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_finalizeEscrowWithdrawal.t.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; +import {TestUtils} from "../TestUtils.sol"; +import {Utils} from "../../src/Utils.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import { + State, + ChannelDefinition, + StateIntent, + Ledger, + EscrowStatus, + ParticipantIndex +} from "../../src/interfaces/Types.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelHubTest_finalizeEscrowWithdrawal is ChannelHubTest_Base { + ChannelDefinition internal def; + bytes32 internal channelId; + State internal initState; + + uint256 constant WITHDRAWAL_AMOUNT = 500; + uint64 constant NON_HOME_CHAIN_ID = 42; + address constant NON_HOME_TOKEN = address(42); + + 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); + + initState = 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, initState); + } + + // ========== StateIntent ========== + + function test_revert_homeChain_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.finalizeEscrowWithdrawal(channelId, bytes32(0), state); + } + + // ========== Challenge Expiry Clearing ========== + + // Regression test: cooperative finalization from DISPUTED must zero out challengeExpireAt. + // Before the fix, _applyEscrowWithdrawalEffects used `if (effects.newChallengeExpiry > 0)` which + // skipped the write when the finalize effects left newChallengeExpiry at 0, leaving a stale + // non-zero value observable via getEscrowWithdrawalData(). + function test_cooperativeFinalize_fromDISPUTED_clearsChallengeExpiry() public { + ChannelDefinition memory altDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 1, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bytes32 altChannelId = Utils.getChannelId(altDef, CHANNEL_HUB_VERSION); + + // Current chain acts as non-home chain; NON_HOME_CHAIN_ID is the home chain. + // Node locks WITHDRAWAL_AMOUNT from its vault on this (non-home) chain. + State memory escrowInitState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: WITHDRAWAL_AMOUNT, + userNetFlow: int256(WITHDRAWAL_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: WITHDRAWAL_AMOUNT, + nodeNetFlow: int256(WITHDRAWAL_AMOUNT) + }), + userSig: "", + nodeSig: "" + }); + escrowInitState = mutualSignStateBothWithEcdsaValidator(escrowInitState, altChannelId, ALICE_PK); + + cHub.initiateEscrowWithdrawal(altDef, escrowInitState); + bytes32 escrowId = Utils.getEscrowId(altChannelId, escrowInitState.version); + + // Challenge → status becomes DISPUTED, challengeExpireAt set to a future timestamp. + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(altChannelId, escrowInitState, ALICE_PK); + cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.USER); + + (, EscrowStatus disputedStatus, uint64 challengeExpiryAfterChallenge,,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(disputedStatus), uint8(EscrowStatus.DISPUTED), "Should be DISPUTED after challenge"); + assertGt(challengeExpiryAfterChallenge, 0, "challengeExpireAt should be non-zero after challenge"); + + // Cooperatively finalize before the challenge period expires. + // User allocation on home decreases by WITHDRAWAL_AMOUNT; node releases locked funds to user. + State memory finalizeState = TestUtils.nextState( + escrowInitState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [uint256(0), uint256(0)], + [int256(WITHDRAWAL_AMOUNT), -int256(WITHDRAWAL_AMOUNT)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [-int256(WITHDRAWAL_AMOUNT), int256(WITHDRAWAL_AMOUNT)] + ); + finalizeState = mutualSignStateBothWithEcdsaValidator(finalizeState, altChannelId, ALICE_PK); + cHub.finalizeEscrowWithdrawal(altChannelId, escrowId, finalizeState); + + // Assert: status FINALIZED and challengeExpireAt cleared to zero. + (, EscrowStatus finalStatus, uint64 finalChallengeExpiry,,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq( + uint8(finalStatus), uint8(EscrowStatus.FINALIZED), "Should be FINALIZED after cooperative finalization" + ); + assertEq( + finalChallengeExpiry, 0, "challengeExpireAt must be cleared after cooperative finalization from DISPUTED" + ); + } + + function test_revert_nonHomeChain_ifWrongIntent() public { + // Use a different nonce so this channel does not exist on the current chain (non-home path). + ChannelDefinition memory altDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 1, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bytes32 altChannelId = Utils.getChannelId(altDef, CHANNEL_HUB_VERSION); + + // Current chain acts as non-home chain; NON_HOME_CHAIN_ID is the home chain. + State memory escrowInitState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: WITHDRAWAL_AMOUNT, + userNetFlow: int256(WITHDRAWAL_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: WITHDRAWAL_AMOUNT, + nodeNetFlow: int256(WITHDRAWAL_AMOUNT) + }), + userSig: "", + nodeSig: "" + }); + escrowInitState = mutualSignStateBothWithEcdsaValidator(escrowInitState, altChannelId, ALICE_PK); + + // Channel is VOID here, so initiateEscrowWithdrawal takes the non-home path and writes metadata. + cHub.initiateEscrowWithdrawal(altDef, escrowInitState); + bytes32 escrowId = Utils.getEscrowId(altChannelId, escrowInitState.version); + + // Finalize with wrong intent — must exercise the non-home metadata path and revert. + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.finalizeEscrowWithdrawal(altChannelId, escrowId, state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_finalizeMigration.t.sol b/contracts/test/ChannelHub_units/ChannelHub_finalizeMigration.t.sol new file mode 100644 index 000000000..bee75ca05 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_finalizeMigration.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_finalizeMigration is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.finalizeMigration(bytes32(0), state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_initiateEscrowDeposit.t.sol b/contracts/test/ChannelHub_units/ChannelHub_initiateEscrowDeposit.t.sol new file mode 100644 index 000000000..cd2bd85f8 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_initiateEscrowDeposit.t.sol @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; +import {TestUtils} from "../TestUtils.sol"; +import {Utils} from "../../src/Utils.sol"; +import {ChannelHub} from "../../src/ChannelHub.sol"; +import { + State, + ChannelDefinition, + StateIntent, + Ledger, + ChannelStatus, + EscrowStatus +} from "../../src/interfaces/Types.sol"; + +// forge-lint: disable-next-item(unsafe-typecast) +contract ChannelHubTest_initiateEscrowDeposit is ChannelHubTest_Base { + ChannelDefinition internal def; + bytes32 internal channelId; + State internal initState; + State internal escrowState; + + uint256 constant ESCROW_AMOUNT = 500; + uint64 constant NON_HOME_CHAIN_ID = 42; + address constant NON_HOME_TOKEN = address(42); + + 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); + + initState = 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: TestUtils.emptyLedger(), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, initState); + + // Build INITIATE_ESCROW_DEPOSIT: node locks ESCROW_AMOUNT on home chain, + // user will lock ESCROW_AMOUNT on non-home chain. + escrowState = TestUtils.nextState( + initState, + StateIntent.INITIATE_ESCROW_DEPOSIT, + [uint256(DEPOSIT_AMOUNT), uint256(ESCROW_AMOUNT)], + [int256(DEPOSIT_AMOUNT), int256(ESCROW_AMOUNT)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(ESCROW_AMOUNT), uint256(0)], + [int256(ESCROW_AMOUNT), int256(0)] + ); + escrowState = mutualSignStateBothWithEcdsaValidator(escrowState, channelId, ALICE_PK); + } + + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.initiateEscrowDeposit(def, state); + } + + // ========== INITIATE_ESCROW_DEPOSIT caller restriction ========== + + function test_revert_homeChain_callerNotNode() public { + vm.expectRevert(ChannelHub.IncorrectMsgSender.selector); + vm.prank(alice); + cHub.initiateEscrowDeposit(def, escrowState); + } + + function test_revert_homeChain_ifETHSent() public { + vm.deal(node, 1); + vm.expectRevert(ChannelHub.IncorrectValue.selector); + vm.prank(node); + cHub.initiateEscrowDeposit{value: 1}(def, escrowState); + } + + function test_homeChain_nodeCanSubmit() public { + uint256 nodeBalanceBefore = cHub.getNodeBalance(address(token)); + + vm.prank(node); + cHub.initiateEscrowDeposit(def, escrowState); + + // Channel state advanced and node funds locked + verifyChannelData(channelId, ChannelStatus.OPERATING, 1, 0, "State should advance after node submits"); + assertEq( + cHub.getNodeBalance(address(token)), + nodeBalanceBefore - ESCROW_AMOUNT, + "Node balance should decrease by escrow amount" + ); + } + + // ========== Non-home native deposit ========== + + function test_nonHomeChain_nativeDeposit_acceptsExactETH() public { + (ChannelDefinition memory nonHomeDef, bytes32 nonHomeChannelId, State memory nativeEscrowState) = + _buildNonHomeNativeEscrowDeposit(); + bytes32 escrowId = Utils.getEscrowId(nonHomeChannelId, nativeEscrowState.version); + + uint256 hubBalanceBefore = address(cHub).balance; + vm.deal(alice, ESCROW_AMOUNT); + vm.prank(alice); + cHub.initiateEscrowDeposit{value: ESCROW_AMOUNT}(nonHomeDef, nativeEscrowState); + + (, EscrowStatus status,,, uint256 lockedAmount,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(status), uint8(EscrowStatus.INITIALIZED), "Escrow should be initialized"); + assertEq(lockedAmount, ESCROW_AMOUNT, "Escrow should lock native ETH"); + assertEq(address(cHub).balance, hubBalanceBefore + ESCROW_AMOUNT, "Native ETH should be pulled"); + } + + function test_revert_nonHomeChain_nativeDeposit_wrongValue() public { + (ChannelDefinition memory nonHomeDef,, State memory nativeEscrowState) = _buildNonHomeNativeEscrowDeposit(); + + vm.deal(alice, ESCROW_AMOUNT - 1); + vm.expectRevert(ChannelHub.IncorrectValue.selector); + vm.prank(alice); + cHub.initiateEscrowDeposit{value: ESCROW_AMOUNT - 1}(nonHomeDef, nativeEscrowState); + } + + function _buildNonHomeNativeEscrowDeposit() + internal + view + returns (ChannelDefinition memory nonHomeDef, bytes32 nonHomeChannelId, State memory nativeEscrowState) + { + nonHomeDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 1, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + nonHomeChannelId = Utils.getChannelId(nonHomeDef, CHANNEL_HUB_VERSION); + + nativeEscrowState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: ESCROW_AMOUNT, + userNetFlow: int256(ESCROW_AMOUNT), + nodeAllocation: ESCROW_AMOUNT, + nodeNetFlow: int256(ESCROW_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(0), + decimals: 18, + userAllocation: ESCROW_AMOUNT, + userNetFlow: int256(ESCROW_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + nativeEscrowState = mutualSignStateBothWithEcdsaValidator(nativeEscrowState, nonHomeChannelId, ALICE_PK); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_initiateEscrowWithdrawal.t.sol b/contracts/test/ChannelHub_units/ChannelHub_initiateEscrowWithdrawal.t.sol new file mode 100644 index 000000000..47ea02c65 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_initiateEscrowWithdrawal.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {ChannelDefinition, State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_initiateEscrowWithdrawal is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + ChannelDefinition memory def; + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.initiateEscrowWithdrawal(def, state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_initiateMigration.t.sol b/contracts/test/ChannelHub_units/ChannelHub_initiateMigration.t.sol new file mode 100644 index 000000000..5da918e66 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_initiateMigration.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {ChannelDefinition, State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_initiateMigration is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + ChannelDefinition memory def; + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.initiateMigration(def, state); + } +} diff --git a/contracts/test/ChannelHub_units/ChannelHub_withdrawFromChannel.t.sol b/contracts/test/ChannelHub_units/ChannelHub_withdrawFromChannel.t.sol new file mode 100644 index 000000000..0effb89b1 --- /dev/null +++ b/contracts/test/ChannelHub_units/ChannelHub_withdrawFromChannel.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {State, StateIntent} from "../../src/interfaces/Types.sol"; + +contract ChannelHubTest_withdrawFromChannel is ChannelHubTest_Base { + // ========== StateIntent ========== + + function test_revert_ifWrongIntent() public { + State memory state; + state.intent = StateIntent.DEPOSIT; + + vm.expectRevert(ChannelHub.IncorrectStateIntent.selector); + cHub.withdrawFromChannel(bytes32(0), state); + } + + // ========== Payable ========== + + // The EVM rejects ETH at the dispatcher level before any Solidity code runs, + // producing an empty revert (no error selector). + + function test_revert_ifETHSent() public { + State memory state; + + vm.deal(address(this), 1); + (bool success, bytes memory returnData) = + address(cHub).call{value: 1}(abi.encodeCall(cHub.withdrawFromChannel, (bytes32(0), state))); + + assertFalse(success, "withdrawFromChannel must not accept ETH"); + assertEq(returnData.length, 0, "Non-payable rejection produces no error data"); + } +} diff --git a/contracts/test/EscrowDepositEngine/EscrowDepositEngine_validateTransition.t.sol b/contracts/test/EscrowDepositEngine/EscrowDepositEngine_validateTransition.t.sol new file mode 100644 index 000000000..65897d98c --- /dev/null +++ b/contracts/test/EscrowDepositEngine/EscrowDepositEngine_validateTransition.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; + +import {EscrowDepositEngine} from "../../src/EscrowDepositEngine.sol"; +import {EscrowStatus, State, StateIntent, Ledger} from "../../src/interfaces/Types.sol"; +import {TestUtils} from "../TestUtils.sol"; + +contract EscrowDepositEngineTest_ValidateTransition is Test { + // nonHomeLedger.token must match the token stored in initState. + // Fund movement on finalize already reads the token from the stored initState, + // so no direct drain is possible via this engine. But accepting a mismatched + // token in the finalize state would corrupt on-chain state consistency. + function test_revert_tokenMismatch_onFinalize() public { + uint64 homeChainId = 42; // not the current chain + address initToken = address(0xAAAA); // token locked at initiation + address otherToken = address(0); // attacker substitutes native ETH + + EscrowDepositEngine.TransitionContext memory ctx; + ctx.status = EscrowStatus.INITIALIZED; + ctx.lockedAmount = 500; + ctx.initState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: homeChainId, + token: address(0xDEAD), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 500, + nodeNetFlow: int256(500) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: initToken, + decimals: 18, + userAllocation: 500, + userNetFlow: int256(500), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + + // Finalize candidate uses a different nonHomeLedger token. + // Universal validation passes (address(0) is valid native token with decimals 18). + // EscrowTokenMismatch fires inside _calculateFinalizeEffects. + State memory candidate = TestUtils.nextState( + ctx.initState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [uint256(500), uint256(0)], + [int256(0), int256(500)], + uint64(block.chainid), + otherToken, // different from initToken + [uint256(0), uint256(0)], + [int256(0), int256(0)] + ); + + vm.expectRevert(EscrowDepositEngine.EscrowTokenMismatch.selector); + EscrowDepositEngine.validateTransition(ctx, candidate); + } +} diff --git a/contracts/test/EscrowWithdrawalEngine/EscrowWithdrawalEngine_validateTransition.t.sol b/contracts/test/EscrowWithdrawalEngine/EscrowWithdrawalEngine_validateTransition.t.sol new file mode 100644 index 000000000..d898db5cf --- /dev/null +++ b/contracts/test/EscrowWithdrawalEngine/EscrowWithdrawalEngine_validateTransition.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; + +import {EscrowWithdrawalEngine} from "../../src/EscrowWithdrawalEngine.sol"; +import {EscrowStatus, State, StateIntent, Ledger} from "../../src/interfaces/Types.sol"; +import {TestUtils} from "../TestUtils.sol"; + +contract EscrowWithdrawalEngineTest_ValidateTransition is Test { + // nonHomeLedger.token must match the token stored in initState. + // Fund movement on finalize reads the token from the stored initState, so no + // direct drain is possible via this engine. But accepting a mismatched token in + // the finalize state would corrupt on-chain state consistency. + function test_revert_tokenMismatch_onFinalize() public { + uint64 homeChainId = 42; // not the current chain + address initToken = address(0xAAAA); // token locked at initiation + address otherToken = address(0); // attacker substitutes native ETH + + EscrowWithdrawalEngine.TransitionContext memory ctx; + ctx.status = EscrowStatus.INITIALIZED; + ctx.lockedAmount = 750; + ctx.nodeAvailableFunds = 0; + ctx.initState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: homeChainId, + token: address(0xDEAD), + decimals: 18, + userAllocation: 750, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: initToken, + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 750, + nodeNetFlow: int256(750) + }), + userSig: "", + nodeSig: "" + }); + + // Finalize candidate uses a different nonHomeLedger token. + // Universal validation passes (address(0) is valid native token with decimals 18). + // EscrowTokenMismatch fires inside _calculateFinalizeEffects. + State memory candidate = TestUtils.nextState( + ctx.initState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [uint256(0), uint256(0)], + [int256(0), int256(0)], + uint64(block.chainid), + otherToken, // different from initToken + [uint256(0), uint256(0)], + [int256(0), int256(0)] + ); + + vm.expectRevert(EscrowWithdrawalEngine.EscrowTokenMismatch.selector); + EscrowWithdrawalEngine.validateTransition(ctx, candidate); + } +} diff --git a/contracts/test/TestChannelHub.sol b/contracts/test/TestChannelHub.sol index b27491b86..472bf95c0 100644 --- a/contracts/test/TestChannelHub.sol +++ b/contracts/test/TestChannelHub.sol @@ -3,13 +3,15 @@ pragma solidity 0.8.30; import {ChannelHub} from "../src/ChannelHub.sol"; import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; +import {EscrowStatus} from "../src/interfaces/Types.sol"; /** * @title TestChannelHub * @notice Test harness contract that exposes internal ChannelHub functions for testing */ +// forge-lint: disable-next-item(mixed-case-function) contract TestChannelHub is ChannelHub { - constructor(ISignatureValidator _defaultSigValidator) ChannelHub(_defaultSigValidator) {} + constructor(ISignatureValidator _defaultSigValidator, address _node) ChannelHub(_defaultSigValidator, _node) {} /** * @notice Marks this contract as a test contract for Forge @@ -18,10 +20,10 @@ contract TestChannelHub is ChannelHub { function IS_TEST() external pure {} /** - * @notice Exposed version of _pushFunds for testing + * @notice Exposed version of _nonRevertingPushFunds for testing */ - function exposed_pushFunds(address to, address token, uint256 amount) external payable { - _pushFunds(to, token, amount); + function exposed_nonRevertingPushFunds(address to, address token, uint256 amount) external payable { + _nonRevertingPushFunds(to, token, amount); } /** @@ -31,6 +33,15 @@ contract TestChannelHub is ChannelHub { _pullFunds(from, token, amount); } + /** + * @notice Workaround to set the node balance directly for testing + * @dev Allows tests to set up node state without going through depositToNode (useful for tokens + * that revert on transferFrom, making a normal deposit impossible) + */ + function workaround_setNodeBalance(address token, uint256 amount) external { + _nodeBalances[token] = amount; + } + /** * @notice Workaround to set reclaim balance for testing * @dev Allows tests to set up reclaim state without going through failed transfers @@ -38,4 +49,63 @@ contract TestChannelHub is ChannelHub { function workaround_setReclaim(address account, address token, uint256 amount) external { _reclaims[account][token] = amount; } + + /** + * @notice Workaround to write an escrow deposit record directly into storage + * @dev Only sets the fields relevant to purge/stats logic; avoids going through the full escrow lifecycle + */ + function workaround_setEscrowDeposit( + bytes32 escrowId, + bytes32 channelId, + EscrowStatus status, + address user_, + address, + /* node_ */ + uint64 unlockAt, + uint64 challengeExpireAt, + uint256 lockedAmount, + address token + ) external { + EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; + meta.channelId = channelId; + meta.status = status; + meta.user = user_; + meta.unlockAt = unlockAt; + meta.challengeExpireAt = challengeExpireAt; + meta.lockedAmount = lockedAmount; + meta.initState.nonHomeLedger.token = token; + } + + /** + * @notice Workaround to append an escrow ID to the ordered purge queue + */ + function workaround_addEscrowDepositId(bytes32 escrowId) external { + _escrowDepositIds.push(escrowId); + } + + /** + * @notice Exposes the internal _escrowDepositIds array for assertions + */ + function harness_escrowDepositIds() external view returns (bytes32[] memory) { + return _escrowDepositIds; + } + + /** + * @notice Exposes the internal _purgeEscrowDeposits for direct invocation in tests + */ + function harness_purgeEscrowDeposits(uint256 maxSteps) external { + _purgeEscrowDeposits(maxSteps); + } + + /** + * @notice Exposed version of _extractValidator for testing + * @dev Returns only the resolved validator address; sigData slice is not useful to callers + */ + function exposed_extractValidator(bytes calldata signature, uint256 approvedSignatureValidators) + external + view + returns (ISignatureValidator validator) + { + (validator,) = _extractValidator(signature, approvedSignatureValidators); + } } diff --git a/contracts/test/TestUtils.sol b/contracts/test/TestUtils.sol index 4b3ac529a..9d1eed889 100644 --- a/contracts/test/TestUtils.sol +++ b/contracts/test/TestUtils.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.30; import {Vm} from "forge-std/Vm.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {DEFAULT_SIG_VALIDATOR_ID, State} from "../src/interfaces/Types.sol"; +import {DEFAULT_SIG_VALIDATOR_ID, State, Ledger, StateIntent} from "../src/interfaces/Types.sol"; import {SessionKeyAuthorization, toSigningData} from "../src/sigValidators/SessionKeyValidator.sol"; import {Utils} from "../src/Utils.sol"; @@ -57,12 +57,133 @@ library TestUtils { return SessionKeyAuthorization({sessionKey: sessionKey, metadataHash: metadataHash, authSignature: signature}); } - function buildAndSignValidatorRegistration(Vm vm, uint8 validatorId, address validatorAddress, uint256 nodePk) + function buildAndSignValidatorRegistration( + Vm vm, + uint8 validatorId, + address validatorAddress, + uint256 nodePk, + address channelHub + ) internal view returns (bytes memory) { + bytes memory message = Utils.getValidatorRegistrationMessage(channelHub, validatorId, validatorAddress); + return signEip191(vm, nodePk, message); + } + + function emptyLedger() internal pure returns (Ledger memory) { + return Ledger({ + chainId: 0, + token: address(0), + decimals: 0, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 0 + }); + } + + function nextState(State memory state, StateIntent intent, uint256[2] memory allocations, int256[2] memory netFlows) internal - view - returns (bytes memory) + pure + returns (State memory) { - bytes memory message = abi.encode(validatorId, validatorAddress, block.chainid); - return signEip191(vm, nodePk, message); + 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, + uint64 nonHomeChainId, + address nonHomeChainToken, + 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: "" + }); + } + + function nextState( + State memory state, + StateIntent intent, + uint256[2] memory allocations, + int256[2] memory netFlows, + uint64 nonHomeChainId, + address nonHomeChainToken, + uint8 nonHomeDecimals, + 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: "" + }); } } diff --git a/contracts/test/Utils.t.sol b/contracts/test/Utils.t.sol index 1aca53ea8..485916f29 100644 --- a/contracts/test/Utils.t.sol +++ b/contracts/test/Utils.t.sol @@ -154,6 +154,27 @@ contract UtilsTest is Test { console.logBytes32(channelId); } + function test_getChannelId_matchesAbiEncodePath() public pure { + ChannelDefinition memory def = ChannelDefinition({ + challengeDuration: 86400, + user: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, + node: 0x435d4B6b68e1083Cc0835D1F971C4739204C1d2a, + nonce: 42, + approvedSignatureValidators: 24042, + metadata: 0x13730b0d8e1bdbdc000000000000000000000000000000000000000000000000 + }); + uint8 version = 1; + + // Reproduce the pre-assembly path: keccak256(abi.encode(def)) + version in first byte + bytes32 baseId = keccak256(abi.encode(def)); + bytes32 expected = bytes32( + (uint256(baseId) & 0x00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + | (uint256(version) << 248) + ); + + assertEq(Utils.getChannelId(def, version), expected); + } + function test_log_calculateEscrowId() public pure { bytes32 channelId = 0xeac2bed767671a8ab77527e1e2fff00bb2e62de5467d9ba3a4105dad5c6e3d66; uint64 version = 42; @@ -177,14 +198,14 @@ contract UtilsTest is Test { bytes memory encoded = toSigningData(auth); console.log("SessionKeyAuthorization signing data:"); - // 0x0000000000000000000000003fba6f40f2cd5b4d833e0305061b88f029ea504459c85ca0f1634dd72294eec96f6d613893a9d3add6943b6f32f9cf7df0858239 + // 0x251773da8b8949935ef07284d20cc8605ad7d6f4cf6b5e040ce07dae857f0b6c0000000000000000000000003fba6f40f2cd5b4d833e0305061b88f029ea504459c85ca0f1634dd72294eec96f6d613893a9d3add6943b6f32f9cf7df0858239 console.logBytes(encoded); // 0x5C372EBfC9029aFe7F7506a4d9586604A40e117F uint256 privKey = 0xefc7c391f4ce326e149bb9943658998227094eb3e0acc92c343241e22fbc7624; bytes memory authSignature = TestUtils.signEip191(vm, privKey, encoded); console.log("Auth signature:"); - // 0x0dd14906d8d4dc22ffe48c6fbec99323e1fd149c71fdd9a5fafbc584eed1e9cb00f4e761bb12a7b7271f760ee77f58a7feb9c89b7d70f1e07fcfa60846a74bd41b + // 0x287f031625a7a3ac8329d7746c5370c30ffad9857b6e0d022296af4bcad9ca4e0b1b680c3231f5508a1a01406697349afedff09b44c4a26a7881aa262c8bbcee1b console.logBytes(authSignature); auth.authSignature = authSignature; diff --git a/contracts/test/WadMath.t.sol b/contracts/test/WadMath.t.sol index afdc5e564..3d4723e79 100644 --- a/contracts/test/WadMath.t.sol +++ b/contracts/test/WadMath.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.30; import {Test} from "forge-std/Test.sol"; import {WadMath} from "../src/WadMath.sol"; +// forge-lint: disable-next-item(mixed-case-function) contract TestWadMath { using WadMath for uint256; using WadMath for int256; diff --git a/contracts/test/mocks/DonatingERC20.sol b/contracts/test/mocks/DonatingERC20.sol new file mode 100644 index 000000000..2297bfcaf --- /dev/null +++ b/contracts/test/mocks/DonatingERC20.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @title DonatingERC20 + * @notice Mock ERC20 that simulates an ERC777 tokensReceived hook donating tokens back to ChannelHub + * @dev During transfer, performs the real transfer and then mints extra tokens to `DONATION_TARGET`, + * increasing its balance. This replicates the scenario where a recipient's ERC777 hook sends + * tokens back to ChannelHub mid-transfer, causing a balance-based success check to misfire. + */ +contract DonatingERC20 is ERC20 { + address public immutable DONATION_TARGET; + uint256 public immutable DONATION_AMOUNT; + + constructor(address donationTarget, uint256 donationAmount) ERC20("Donating Token", "DON") { + DONATION_TARGET = donationTarget; + DONATION_AMOUNT = donationAmount; + } + + function mint(address to, uint256 amount) public { + _mint(to, amount); + } + + function transfer(address to, uint256 amount) public override returns (bool) { + bool success = super.transfer(to, amount); + // Simulate ERC777 tokensReceived hook: recipient donates tokens back to ChannelHub, + // increasing ChannelHub's balance above (balanceBefore - amount) + _mint(DONATION_TARGET, DONATION_AMOUNT); + return success; + } +} diff --git a/contracts/test/mocks/MalformedReturningERC20.sol b/contracts/test/mocks/MalformedReturningERC20.sol index 5ba972fba..d4c4289a8 100644 --- a/contracts/test/mocks/MalformedReturningERC20.sol +++ b/contracts/test/mocks/MalformedReturningERC20.sol @@ -17,7 +17,11 @@ contract MalformedReturningERC20 is ERC20 { // Return only 1 byte instead of 32 (malformed) WITHOUT actually transferring // This simulates a malicious/buggy token that returns invalid data assembly { - mstore(0, 1) + // mstore8 writes a single byte (0x01) to address 0. + // mstore(0, 1) would write the value 1 as a 32-byte big-endian word, + // placing 0x01 at address 31 and 0x00 at address 0 — so return(0, 1) + // would yield 0x00, not 0x01. mstore8 avoids this by writing exactly one byte. + mstore8(0, 1) return(0, 1) } } @@ -26,7 +30,11 @@ contract MalformedReturningERC20 is ERC20 { // Return only 1 byte instead of 32 (malformed) WITHOUT actually transferring // This simulates a malicious/buggy token that returns invalid data assembly { - mstore(0, 1) + // mstore8 writes a single byte (0x01) to address 0. + // mstore(0, 1) would write the value 1 as a 32-byte big-endian word, + // placing 0x01 at address 31 and 0x00 at address 0 — so return(0, 1) + // would yield 0x00, not 0x01. mstore8 avoids this by writing exactly one byte. + mstore8(0, 1) return(0, 1) } } diff --git a/contracts/test/mocks/OversizedReturnERC20.sol b/contracts/test/mocks/OversizedReturnERC20.sol new file mode 100644 index 000000000..d099d3481 --- /dev/null +++ b/contracts/test/mocks/OversizedReturnERC20.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @title OversizedReturnERC20 + * @notice Mock ERC20 whose transfer() returns `returnDataSize` bytes with `firstWord` as the first word. + * @dev Used to verify that _trySafeTransfer caps returndata copy to 32 bytes, preventing + * memory-expansion OOG when a token returns an oversized buffer. + * The actual transfer is performed only when firstWord != 0, matching normal token semantics + * (a zero return value signals failure without a state change). + */ +contract OversizedReturnERC20 is ERC20 { + uint256 private immutable RETURN_DATA_SIZE; + uint256 private immutable FIRST_WORD; + + constructor(uint256 returnDataSize, uint256 firstWord) ERC20("Oversized Token", "OVR") { + RETURN_DATA_SIZE = returnDataSize; + FIRST_WORD = firstWord; + } + + function transfer(address to, uint256 amount) public override returns (bool) { + if (FIRST_WORD != 0) { + _transfer(msg.sender, to, amount); + } + + uint256 size = RETURN_DATA_SIZE; + uint256 word = FIRST_WORD; + assembly { + let ptr := mload(0x40) + mstore(ptr, word) + // EVM zero-initialises newly expanded memory, so bytes [32, size) are 0x00. + return(ptr, size) + } + } + + function mint(address to, uint256 amount) public { + _mint(to, amount); + } +} diff --git a/contracts/test/sigValidators/ECDSAValidator.t.sol b/contracts/test/sigValidators/ECDSAValidator.t.sol index 0e75b82f1..9fb094ae6 100644 --- a/contracts/test/sigValidators/ECDSAValidator.t.sol +++ b/contracts/test/sigValidators/ECDSAValidator.t.sol @@ -81,3 +81,62 @@ contract ECDSAValidatorTest_validateSignature is ECDSAValidatorTest_Base { assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); } } + +contract ECDSAValidatorTest_validateChallengeSignature is ECDSAValidatorTest_Base { + function _signChallenge(uint256 pk, bool eip191) internal pure returns (bytes memory) { + bytes memory challengeSigningData = abi.encodePacked(SIGNING_DATA, "challenge"); + bytes memory message = Utils.pack(CHANNEL_ID, challengeSigningData); + return eip191 ? TestUtils.signEip191(vm, pk, message) : TestUtils.signRaw(vm, pk, message); + } + + function test_success_withEip191Sig() public view { + bytes memory signature = _signChallenge(USER_PK, true); + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_SUCCESS)); + } + + function test_success_withRawEcdsaSig() public view { + bytes memory signature = _signChallenge(USER_PK, false); + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_SUCCESS)); + } + + function test_appends_challenge_suffix() public view { + // A signature valid for plain signingData must NOT pass challenge validation + bytes memory plainMessage = Utils.pack(CHANNEL_ID, SIGNING_DATA); + bytes memory signature = TestUtils.signEip191(vm, USER_PK, plainMessage); + + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); + } + + function test_failure_withWrongSigner_eip191() public view { + bytes memory signature = _signChallenge(OTHER_SIGNER_PK, true); + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); + } + + function test_failure_withWrongSigner_raw() public view { + bytes memory signature = _signChallenge(OTHER_SIGNER_PK, false); + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); + } + + function test_failure_withOtherChannelId_eip191() public view { + bytes memory challengeSigningData = abi.encodePacked(SIGNING_DATA, "challenge"); + bytes memory message = Utils.pack(OTHER_CHANNEL_ID, challengeSigningData); + bytes memory signature = TestUtils.signEip191(vm, USER_PK, message); + + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); + } + + function test_failure_withOtherChannelId_raw() public view { + bytes memory challengeSigningData = abi.encodePacked(SIGNING_DATA, "challenge"); + bytes memory message = Utils.pack(OTHER_CHANNEL_ID, challengeSigningData); + bytes memory signature = TestUtils.signRaw(vm, USER_PK, message); + + ValidationResult result = validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); + } +} diff --git a/contracts/test/sigValidators/SessionKeyValidator.t.sol b/contracts/test/sigValidators/SessionKeyValidator.t.sol index df0ae013c..5e62408a2 100644 --- a/contracts/test/sigValidators/SessionKeyValidator.t.sol +++ b/contracts/test/sigValidators/SessionKeyValidator.t.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.30; -import {Test} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; import {TestUtils} from "../TestUtils.sol"; import { SessionKeyValidator, SessionKeyAuthorization, + SESSION_KEY_AUTH_TYPEHASH, toSigningData } from "../../src/sigValidators/SessionKeyValidator.sol"; import {ValidationResult, VALIDATION_SUCCESS, VALIDATION_FAILURE} from "../../src/interfaces/ISignatureValidator.sol"; @@ -208,4 +209,55 @@ contract SessionKeyValidatorTest_validateSignature is SessionKeyValidatorTest_Ba ValidationResult result = validator.validateSignature(CHANNEL_ID, SIGNING_DATA, signature, user); assertEq(ValidationResult.unwrap(result), ValidationResult.unwrap(VALIDATION_FAILURE)); } + + function test_log_toSigningData() public pure { + address FIXED_SESSION_KEY = address(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF); + bytes32 FIXED_METADATA_HASH = + bytes32(uint256(0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890)); + + SessionKeyAuthorization memory skAuth = SessionKeyAuthorization({ + sessionKey: FIXED_SESSION_KEY, metadataHash: FIXED_METADATA_HASH, authSignature: "" + }); + + bytes memory encoded = toSigningData(skAuth); + + // Verify full 96-byte golden value: typehash || padded address || metadataHash. + bytes memory expected = hex"251773da8b8949935ef07284d20cc8605ad7d6f4cf6b5e040ce07dae857f0b6c" + hex"000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + hex"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; + assertEq(encoded.length, 96); + assertEq(encoded, expected); + + // Verify typehash is the first 32 bytes of the encoded payload. + bytes32 typehashWord; + assembly { + typehashWord := mload(add(encoded, 32)) + } + assertEq(typehashWord, SESSION_KEY_AUTH_TYPEHASH); + + console.log("SESSION_KEY_AUTH_TYPEHASH:"); + // 0x251773da8b8949935ef07284d20cc8605ad7d6f4cf6b5e040ce07dae857f0b6c + console.logBytes32(SESSION_KEY_AUTH_TYPEHASH); + + console.log("toSigningData output (96 bytes: typehash || sessionKey || metadataHash):"); + // 0x251773da8b8949935ef07284d20cc8605ad7d6f4cf6b5e040ce07dae857f0b6c000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeefabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 + console.logBytes(encoded); + } +} + +contract SessionKeyValidatorTest_validateChallengeSignature is SessionKeyValidatorTest_Base { + function test_revert_challengeWithSessionKeyNotSupported() public { + // Signature contents are irrelevant — the method always reverts regardless of input. + SessionKeyAuthorization memory skAuth = createSkAuth(sessionKey1, METADATA_HASH, USER_PK, true); + bytes memory skSignature = signChallengeWithSk(CHANNEL_ID, SIGNING_DATA, SESSION_KEY1_PK, true); + bytes memory signature = abi.encode(skAuth, skSignature); + + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, signature, user); + } + + function test_revert_challengeWithSessionKeyNotSupported_emptySignature() public { + vm.expectRevert(SessionKeyValidator.ChallengeWithSessionKeyNotSupported.selector); + validator.validateChallengeSignature(CHANNEL_ID, SIGNING_DATA, "", user); + } } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e4f4fdf4e..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,174 +0,0 @@ -services: - # TODO: add a frontend client to manage channels. - database: - image: postgres:17.2 - environment: - POSTGRES_USER: ${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} - POSTGRES_DB: ${POSTGRES_DB:-postgres} - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] - interval: 5s - retries: 5 - start_period: 10s - - anvil: - image: ghcr.io/foundry-rs/foundry:v1.2.2 - container_name: anvil - ports: - - "8545:8545" - entrypoint: ["anvil"] - command: ["--host","0.0.0.0","--chain-id","31337","--accounts","15","--balance","30000"] - healthcheck: - test: ["CMD", "cast", "block-number", "--rpc-url", "http://localhost:8545"] - interval: 5s - timeout: 5s - retries: 5 - - # Deploys the contracts to the Anvil instance - # Addresses of the contracts are determined by mnemonic key and derivation index. - # Each contract should have a unique derivation index in order to remain deterministic. - # - # Indices and corresponding addresses: - # Custody(58808): 0x8658501c98C3738026c4e5c361c6C3fa95DfB255 - # DummyAdjudicator(35305): 0xcbbc03a873c11beeFA8D99477E830be48d8Ae6D7 - # USDC ERC20(77360): 0xbD24c53072b9693A35642412227043Ffa5fac382 - # WETH ERC20(88421): 0xAf119209932D7EDe63055E60854E81acC4063a12 - # BalanceChecker(53231): 0x730dB3A1D3Ca47e7BaEb260c24C74ED4378726Bc - contract-deployer: - image: ghcr.io/foundry-rs/foundry:v1.2.2 - container_name: contract-deployer - depends_on: - anvil: - condition: service_healthy - volumes: - - ./contract:/app - working_dir: /app - environment: - RPC_URL: ${RPC_URL:-http://anvil:8545} - DEPLOYER_MNEMONIC: ${DEPLOYER_MNEMONIC:-"test test test test test test test test test test test junk"} - command: > - ' - forge clean && - (forge script script/DeployCustody.s.sol --broadcast --rpc-url $${RPC_URL} --sig "run(uint32,string)" 58808 "$${DEPLOYER_MNEMONIC}" | grep "Deployed") && - (forge script script/DeployDummyAdjudicator.s.sol --broadcast --rpc-url $${RPC_URL} --sig "run(uint32,string)" 35305 "$${DEPLOYER_MNEMONIC}" | grep "Deployed") && - (forge script script/DeployAndFundERC20.s.sol --broadcast --rpc-url $${RPC_URL} --sig "run(uint32,string,string,uint8,string)" 77360 "USD Coin" "USDC" 6 "$${DEPLOYER_MNEMONIC}" | grep "Deployed") && - (forge script script/DeployAndFundERC20.s.sol --broadcast --rpc-url $${RPC_URL} --sig "run(uint32,string,string,uint8,string)" 88421 "Wrapped Ether" "WETH" 18 "$${DEPLOYER_MNEMONIC}" | grep "Deployed") && - (forge script script/DeployBalanceChecker.s.sol --broadcast --rpc-url $${RPC_URL} --sig "run(uint32,string)" 53231 "$${DEPLOYER_MNEMONIC}" | grep "Deployed") - ' - - db-init: - image: postgres:17.2 - environment: - PGUSER: ${POSTGRES_USER:-postgres} - PGPASSWORD: ${POSTGRES_PASSWORD:-postgres} - PGDATABASE: ${POSTGRES_DB:-postgres} - PGHOST: database - PGPORT: 5432 - USDC_TOKEN_ADDRESS: ${USDC_TOKEN_ADDRESS:-0xbD24c53072b9693A35642412227043Ffa5fac382} - WETH_TOKEN_ADDRESS: ${WETH_TOKEN_ADDRESS:-0xAf119209932D7EDe63055E60854E81acC4063a12} - volumes: - - ./clearnode/config/migrations/postgres:/migrations - command: > - sh -c " - echo 'Waiting for database to be ready...' && - until pg_isready -h database -p 5432 -U ${POSTGRES_USER:-postgres}; do - echo 'Waiting for database connection...' - sleep 2 - done && - - # Create migrations table if it doesn't exist - echo 'Creating migrations table if not exists...' && - psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -c \" - CREATE TABLE IF NOT EXISTS goose_db_version ( - id serial PRIMARY KEY, - version_id int8 NOT NULL, - is_applied boolean NOT NULL DEFAULT true, - tstamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - \" && - - echo 'Running pending migrations...' && - for migration in /migrations/*.sql; do - filename=\$$(basename \$$migration) && - version=\$$(echo \$$filename | grep -o '^[0-9]\\+') && - - if ! psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -tAc \"SELECT 1 FROM goose_db_version WHERE version_id = \$$version\" | grep -q 1; then - echo \"Applying migration: \$$filename\" && - # Extract and execute only the Up migration - sed -n '/^-- +goose Up/,/^-- +goose Down/p' \$$migration | - grep -v '^-- +goose' | - psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} && - psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -c \"INSERT INTO goose_db_version (version_id) VALUES (\$$version);\" && - echo \"Successfully applied: \$$filename\" - else - echo \"Skipping already applied migration: \$$filename\" - fi - done && - - echo 'Checking database...' && - # Check if USDC token already exists - USDC_EXISTS=$$(psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -t -c \"SELECT COUNT(*) FROM assets WHERE token = '$$USDC_TOKEN_ADDRESS' AND chain_id = 31337;\" | xargs) && - - if [ \"$$USDC_EXISTS\" -eq \"0\" ]; then - echo 'Seeding database with USDC token...' && - psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -c \" - INSERT INTO assets (token, chain_id, symbol, decimals) - VALUES ('$$USDC_TOKEN_ADDRESS', 31337, 'usdc', 6); - \" && - echo 'USDC token seeded successfully' - else - echo 'USDC token already seeded, skipping' - fi && - - # Check if WETH token already exists - WETH_EXISTS=$$(psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -t -c \"SELECT COUNT(*) FROM assets WHERE token = '$$WETH_TOKEN_ADDRESS' AND chain_id = 31337;\" | xargs) && - - if [ \"$$WETH_EXISTS\" -eq \"0\" ]; then - echo 'Seeding database with WETH token...' && - psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -c \" - INSERT INTO assets (token, chain_id, symbol, decimals) - VALUES ('$$WETH_TOKEN_ADDRESS', 31337, 'eth', 18); - \" && - echo 'WETH token seeded successfully' - else - echo 'WETH token already seeded, skipping' - fi - " - depends_on: - database: - condition: service_healthy - contract-deployer: - condition: service_completed_successfully - - clearnode: - build: - context: ./clearnode - dockerfile: Dockerfile - ports: - - "8000:8000" - - "4242:4242" - depends_on: - database: - condition: service_healthy - contract-deployer: - condition: service_completed_successfully - db-init: - condition: service_completed_successfully - environment: - CLEARNODE_CONFIG_DIR_PATH: ${CLEARNODE_CONFIG_DIR_PATH:-/config} - CLEARNODE_MODE: test - ANVIL_BLOCKCHAIN_RPC: ws://anvil:8545 - CLEARNODE_DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@database:5432/${POSTGRES_DB:-postgres}?sslmode=disable - CLEARNODE_LOG_LEVEL: ${CLEARNODE_LOG_LEVEL:-info} - volumes: - - ./clearnode/config/compose/integration:/config:ro - restart: unless-stopped - -volumes: - postgres_data: - driver: local diff --git a/docs/README.md b/docs/README.md index b05548aa3..f2019d116 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,76 +1,68 @@ -# Nitrolite V1 Clearnode Specifications +# Nitrolite V1 Nitronode 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 Nitronode 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/ -clearnode/ - api/ # AppSessionService - app_session/ - channel/ - user/ - node/ +```text +cerebro/ # Cerebro Testing Client +nitronode/ + 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 6de9fe49b..0188f4f26 100644 --- a/docs/api.yaml +++ b/docs/api.yaml @@ -29,7 +29,7 @@ types: description: Nonce for the channel - name: status type: string - description: Current status of the channel (void, open, challenged, closed) + description: Current status of the channel (void, open, challenged, closing, closed) - name: state_version type: string description: On-chain state version of the channel @@ -49,6 +49,7 @@ types: - escrow_withdraw - migrate - finalize + - challenge_rescue - transition: description: Represents a state transition @@ -279,6 +280,9 @@ types: - name: amount type: string description: Balance amount + - name: enforced + type: string + description: On-chain enforced balance - transaction_type: description: Type of transaction @@ -295,6 +299,7 @@ types: - migrate - rebalance - finalize + - challenge_rescue - transaction: description: Transaction record @@ -376,7 +381,17 @@ types: description: Unix timestamp in seconds indicating when the session key expires - name: user_sig type: string - description: User's signature over the session key metadata to authorize the registration/update of the session key + description: > + User's EIP-191 signature over abi.encode(SESSION_KEY_AUTH_TYPEHASH, session_key, metadata_hash), + where SESSION_KEY_AUTH_TYPEHASH = keccak256("Nitrolite.SessionKey(address sessionKey,bytes32 metadataHash)") + and metadata_hash = keccak256(abi.encode(user_address, version, assets[], expires_at)). + Authorizes the delegation of signing authority to the session key. + - name: session_key_sig + type: string + description: > + Session-key holder's EIP-191 signature over the same payload as user_sig — + abi.encode(SESSION_KEY_AUTH_TYPEHASH, session_key, metadata_hash) — proving possession of the key + being registered. Required on every submit to prevent registration of keys the submitter does not control. - app_session_key_state: description: Represents the state of an app session key @@ -406,6 +421,9 @@ types: - name: user_sig type: string description: User's signature over the session key metadata to authorize the registration/update of the session key + - name: session_key_sig + type: string + description: Session-key holder's signature over the same packed state (which already binds user_address) proving possession of the key being registered. Required on every submit to prevent registration of keys the submitter does not control. - app: description: Application definition @@ -507,7 +525,7 @@ api: - version: v1 methods: - name: get_home_channel - description: Retrieve current on-chain home channel information + description: Retrieve current on-chain home channel information. Returns a successful response with `channel` omitted when no home channel exists for the wallet/asset pair. request: - field_name: wallet type: string @@ -518,12 +536,18 @@ api: response: - field_name: channel type: channel - description: The on-chain channel information - errors: - - message: channel_not_found - description: The specified channel was not found + description: The on-chain channel information; omitted when no home channel exists for the wallet/asset pair + optional: true + errors: [] - name: get_escrow_channel - description: Retrieve current on-chain escrow channel information + description: | + Retrieve current on-chain escrow channel information. Returns a successful response with `channel` omitted when no escrow channel exists for the given ID. + + Note: when the escrow channel has been closed by the on-chain + purge queue (no signed FINALIZE_ESCROW_DEPOSIT was received + before expiry), `state_version` on the returned channel reflects + the initiate version (N) and does not advance to the finalize + version (N+1). request: - field_name: escrow_channel_id type: string @@ -531,10 +555,9 @@ api: response: - field_name: channel type: channel - description: The on-chain channel information - errors: - - message: channel_not_found - description: The specified channel was not found + description: The on-chain channel information; omitted when no escrow channel exists for the given ID + optional: true + errors: [] - name: get_channels description: Retrieve all channels for a user with optional filtering request: @@ -567,7 +590,7 @@ api: - message: invalid_parameters description: The request parameters are invalid - name: get_latest_state - description: Retrieve the current state of the user stored on the Node + description: Retrieve the current state of the user stored on the Node. Returns a successful response with `state` omitted when no state is stored for the wallet/asset pair. request: - field_name: wallet type: string @@ -582,47 +605,9 @@ api: response: - field_name: state type: state - description: The current state of the user - errors: - - message: channel_not_found - description: The specified channel was not found - - name: get_states - description: Retrieve state history for a user with optional filtering - request: - - field_name: wallet - type: string - description: The user's wallet address - - field_name: asset - type: string - description: Filter by asset symbol - - field_name: epoch - type: string - description: Filter by user epoch index - optional: true - - field_name: channel_id - type: string - description: Filter by Home/Escrow Channel ID + description: The current state of the user; omitted when no state is stored for the wallet/asset pair optional: true - - field_name: only_signed - type: boolean - description: Return only signed states - - field_name: pagination - type: pagination_params - description: Pagination parameters (offset, limit, sort) - optional: true - response: - - field_name: states - type: array - items: - type: state - description: List of states - - field_name: metadata - type: pagination_metadata - description: Pagination information - optional: true - errors: - - message: invalid_parameters - description: The request parameters are invalid + errors: [] - name: request_creation description: Request the creation of a channel from Node request: @@ -663,17 +648,34 @@ api: - message: denied_until_checkpoint description: State submissions are denied until the next checkpoint - name: submit_session_key_state - description: Submit the session key state for registration and updates + description: | + Submit the channel session key state for registration, update, revocation, or re-activation. + + The `expires_at` timestamp on `state` governs activation: + * A value in the future activates the session key until that time. + * A value at or before "now" revokes the key immediately. The same monotonic + `version` sequence is preserved, so a subsequent submit with `version+1` and + a future `expires_at` re-activates the same session key address. Re-activating + a previously-revoked key counts against the per-user cap, the same as + registering a brand-new one. + The metadata hash binds `expires_at`, so each revoke or re-activation requires a + fresh user signature over the new payload. Negative unix timestamps are rejected. + + Both `user_sig` (wallet) and `session_key_sig` (session-key holder) are required + on every submit, including the revocation path — the session key must co-sign its + own deactivation. Wallet-only revocation (for a lost or compromised key) is not + supported by this method; that flow requires a separate code path and is tracked + as a follow-up. request: - field_name: state type: channel_session_key_state - description: Session key metadata and delegation information + description: Session key metadata and delegation information. Set `expires_at` to a past value to revoke; future to (re-)activate. response: [] errors: - message: invalid_session_key_state description: The session key state is invalid - name: get_last_key_states - description: Retrieve the latest session key states for a user with optional filtering by session key + description: Retrieve the latest session key states for a user with optional filtering by session key. By default only currently active (non-expired) latest states are returned; set `include_inactive` to include expired latest states. Mandatory pagination caps response size (max page size 10). request: - field_name: user_address type: string @@ -682,12 +684,23 @@ api: type: string description: Optionally filter by session key optional: true + - field_name: include_inactive + type: boolean + description: If true, include latest states whose `expires_at` is in the past (expired or revoked). Defaults to false (active-only). + optional: true + - field_name: pagination + type: pagination_params + description: Pagination parameters (offset, limit). Default limit 10, max 10. The `sort` field is not supported by this endpoint and must be omitted. + optional: true response: - field_name: states type: array items: - type: app_session_key_state - description: List of active session key states for the user + type: channel_session_key_state + description: Latest session key states for the user, filtered by `include_inactive`. + - field_name: metadata + type: pagination_metadata + description: Pagination information errors: - message: account_not_found description: The specified account was not found @@ -767,7 +780,7 @@ api: errors: [] - name: get_app_definition - description: Retrieve the application definition for a specific app session + description: Retrieve the application definition for a specific app session. Returns a successful response with `definition` omitted when no app session exists for the given ID. request: - field_name: app_session_id type: string @@ -775,10 +788,9 @@ api: response: - field_name: definition type: app_definition - description: The application definition - errors: - - message: app_session_not_found - description: The specified app session was not found + description: The application definition; omitted when no app session exists for the given ID + optional: true + errors: [] - name: get_app_sessions description: List all application sessions for a participant with optional filtering request: @@ -851,31 +863,59 @@ api: - message: insufficient_balance description: Participant has insufficient balance for allocations - name: submit_session_key_state - description: Submit the session key state for registration and updates + description: | + Submit the app session key state for registration, update, revocation, or re-activation. + + The `expires_at` timestamp on `state` governs activation: + * A value in the future activates the session key until that time. + * A value at or before "now" revokes the key immediately. The same monotonic + `version` sequence is preserved, so a subsequent submit with `version+1` and + a future `expires_at` re-activates the same session key address. Re-activating + a previously-revoked key counts against the per-user cap, the same as + registering a brand-new one. + The metadata hash binds `expires_at`, so each revoke or re-activation requires a + fresh user signature over the new payload. Negative unix timestamps are rejected. + + Both `user_sig` (wallet) and `session_key_sig` (session-key holder) are required + on every submit, including the revocation path — the session key must co-sign its + own deactivation. Wallet-only revocation (for a lost or compromised key) is not + supported by this method; that flow requires a separate code path and is tracked + as a follow-up. request: - field_name: state type: app_session_key_state - description: Session key metadata and delegation information + description: Session key metadata and delegation information. Set `expires_at` to a past value to revoke; future to (re-)activate. response: [] errors: - message: invalid_session_key_state description: The session key state is invalid - name: get_last_key_states - description: Retrieve the latest session key states for a user with optional filtering by session key + description: Retrieve the latest session key states for a user with optional filtering by session key. By default only currently active (non-expired) latest states are returned; set `include_inactive` to include expired latest states. Mandatory pagination caps response size (max page size 10). request: - - field_name: wallet + - field_name: user_address type: string description: User's wallet address - field_name: session_key type: string description: Optionally filter by session key optional: true + - field_name: include_inactive + type: boolean + description: If true, include latest states whose `expires_at` is in the past (expired or revoked). Defaults to false (active-only). + optional: true + - field_name: pagination + type: pagination_params + description: Pagination parameters (offset, limit). Default limit 10, max 10. The `sort` field is not supported by this endpoint and must be omitted. + optional: true response: - field_name: states type: array items: type: app_session_key_state - description: List of active session key states for the user + description: Latest session key states for the user, filtered by `include_inactive`. + - field_name: metadata + type: pagination_metadata + description: Pagination information errors: - message: account_not_found description: The specified account was not found 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..4cf7584c8 100644 --- a/docs/data_models.mmd +++ b/docs/data_models.mmd @@ -1,151 +1,347 @@ 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 + class AppLedgerEntryV1 { + +uuid id PK + +char~66~ account_id FK + +varchar~20~ asset_symbol + +char~42~ wallet + +numeric credit + +numeric debit + +timestamptz created_at + } - +string FromAccount - +string ToAccount + %% ===== 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 + +text session_key_sig + +timestamptz created_at + +timestamptz updated_at + } - +[64]char *SenderNewStateID - +[64]char *ReceiverNewStateID + class AppSessionKeyApplicationV1 { + +char~66~ session_key_state_id PK FK + +varchar~66~ application_id PK FK + } - +decimal.Decimal Amount - +time.Time CreatedAt + class AppSessionKeyAppSessionV1 { + +char~66~ session_key_state_id PK FK + +char~66~ app_session_id PK FK } - class TransactionType { - %% isWallet(ToAccount) && isWallet(FromAccount) -> transfer - transfer + 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 + +text session_key_sig + +timestamptz created_at + } - %% ToAccount = AppSessionID -> commit - %% FromAccount = UserWallet -> commit - commit + class ChannelSessionKeyAssetV1 { + +char~66~ session_key_state_id PK FK + +varchar~20~ asset PK + } - %% ToAccount = UserWallet -> release - %% FromAccount = AppSessionID -> release - release + class CurrentSessionKeyStateV1 { + +char~42~ user_address PK + +char~42~ session_key PK + +smallint kind PK + +numeric version + +timestamptz updated_at + UNIQUE(session_key, kind) + } - %% 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 + + %% -- Pointer table tracks latest version per (user_address, session_key, kind) -- + CurrentSessionKeyStateV1 ..> AppSessionKeyStateV1 : kind=2 -> latest version + CurrentSessionKeyStateV1 ..> ChannelSessionKeyStateV1 : kind=1 -> latest version + + %% -- 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/clearnode/docs/API.md b/docs/legacy/API.md similarity index 100% rename from clearnode/docs/API.md rename to docs/legacy/API.md diff --git a/clearnode/docs/Clearnode.protocol.md b/docs/legacy/Clearnode.protocol.md similarity index 100% rename from clearnode/docs/Clearnode.protocol.md rename to docs/legacy/Clearnode.protocol.md diff --git a/clearnode/docs/Entities.md b/docs/legacy/Entities.md similarity index 100% rename from clearnode/docs/Entities.md rename to docs/legacy/Entities.md diff --git a/clearnode/docs/SessionKeys.md b/docs/legacy/SessionKeys.md similarity index 100% rename from clearnode/docs/SessionKeys.md rename to docs/legacy/SessionKeys.md 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..9d787d7ce --- /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 <> Nitronode 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..caf5ddf5c --- /dev/null +++ b/docs/protocol/cryptography.md @@ -0,0 +1,138 @@ +# 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 an authorization payload constructed as: + `abi.encode(SESSION_KEY_AUTH_TYPEHASH, sessionKey, metadataHash)` + where `SESSION_KEY_AUTH_TYPEHASH = keccak256("Nitrolite.SessionKey(address sessionKey,bytes32 metadataHash)")`, + `sessionKey` is the address of the delegated key, and `metadataHash` is the `keccak256` hash of the + canonically encoded authorization metadata structure. The type hash prefix prevents unrelated + 64-byte signed values from being reused as session-key authorizations. +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. + +### Authorization Metadata + +The authorization metadata structure defines the scope of a session key. It MUST contain at least the following fields, which constitute the minimum scope every implementation MUST enforce: + +| Field | Purpose | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `version` | Replay and revocation guard for the participant's session-key delegations. Issuing a session-key state with a higher version supersedes prior versions, so the previous delegation MUST be treated as revoked once a newer one is registered. | +| `expires_at` | Bounds the lifetime of the delegation; signatures produced after expiry MUST be rejected. | + +Implementations MAY extend the metadata structure with additional restrictions (for example: authorized assets, allowed transitions, channels, recipients, or per-asset spending limits). Restrictions that an implementation does not encode and enforce are not applied: a session key whose authorization does not narrow a given dimension can sign any otherwise-valid state along that dimension, up to the participant's full balance, until the authorization expires. Participants SHOULD scope the metadata fields available to them accordingly and treat session-key compromise as equivalent to control of the participant's authority within the scope they issued, for the duration of the validity window. + +Only the `keccak256` hash of the canonically encoded metadata is bound into the authorization signature; the full structure is supplied alongside the signature at validation time. This indirection lets implementations extend the metadata structure with additional fields purely at the off-chain layer, without changes to the on-chain validator and without invalidating session keys issued under earlier metadata layouts: the on-chain layer only verifies the metadata hash, while the off-chain layer is responsible for decoding the full metadata and enforcing every field it understands. + +## 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..4510fb449 --- /dev/null +++ b/docs/protocol/enforcement.md @@ -0,0 +1,191 @@ +# 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 +- Any party MAY submit the latest mutually signed state to the blockchain layer, provided a valid execution path exists for that state's intent in the current channel context +- 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. OPERATE intent requires the user net flow delta to be zero relative to the previous on-chain state (which is the empty state for a new channel). An OPERATE state that carries accumulated user net flow from an unenforced prior DEPOSIT state cannot be used to create or checkpoint a channel — parties MUST enforce the DEPOSIT state on-chain before advancing to subsequent OPERATE states that depend on it. + +## 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. + +### Off-chain behaviour during dispute + +While a channel is in `CHALLENGED` (i.e. on-chain `DISPUTED`) status, the Node and the user follow additional off-chain rules: + +- **Node stops signing receive states.** Incoming off-chain transfer credits and app-session release credits targeting the channel are not co-signed. They are appended as unsigned entries to the channel's state history (a per-channel queue). +- **User-initiated operations are blocked.** The user cannot deposit, withdraw, transfer, or lock funds into an app session. Only receiving funds (which the Node queues per the previous rule) is permitted. +- **Operations remain blocked even after the challenge timer expires**, until the channel is explicitly closed and `ChannelClosed` event is seen by the Node. + +Two resolution paths: + +- **Challenge cleared** (a newer mutually signed state is enforced, returning the channel to `OPERATING`): the Node signs only the off-chain head — the highest-version queued "receive" state — so the channel's actual latest state is fully co-signed; the user countersigns and acknowledges. Earlier queued entries remain unsigned in history; the head carries forward their cumulative balance impact, so normal flow resumes with no gap and no credit lost. +- **Challenge expires and the channel is closed**: a single `challenge_rescue` state is committed to the user's next epoch, detached from the closed channel — equivalent to the funds arriving while the user did not have a channel. Its amount is the **net effect on the home-channel balance of transitions stored strictly above the closure version** at the closed channel's epoch: receives (`transfer_receive`, `release`) contribute positively; sends (`transfer_send`, `commit`) contribute negatively; other transition kinds (deposit, withdrawal, escrow, migrate, finalize) are excluded because they require onchain backing the chain did not enforce. Signed (pre-challenge) and unsigned (during-challenge) rows both contribute. The result is clamped at zero so an adversarial close at a version where the user's own balance was higher than the off-chain head cannot dock the user further. When no relevant transitions exist above closure, the state carries zero credit and serves only to advance the state chain off the closed channel so future operations are not wedged on it. + +The purpose of these rules is to ensure that a `CHALLENGED` channel cannot have its dispute cleared as a side effect of incoming third-party transfers, and that the user is made whole for net real value owed by the Node regardless of which resolution path is taken. + +## 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..1336b2451 --- /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, provided a valid execution path exists for that state's intent in the current channel context. + +## 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, provided a valid execution path exists for that state's intent +- **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 mutually signed state to the blockchain layer for on-chain enforcement, provided that state's intent has a valid execution path in the current channel context. 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..0bcb6ec3b --- /dev/null +++ b/docs/protocol/security-and-limitations.md @@ -0,0 +1,96 @@ +# 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 be enforced on-chain when a valid execution path exists for its intent; parties MUST retain any intermediate states required to establish that path +- **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 mutually signed state to the blockchain layer; enforcement succeeds when a valid execution path exists for that state's intent in the current channel context +- Parties MUST retain and enforce intermediate states (such as a DEPOSIT state) before discarding them — a subsequent OPERATE state built on top of an unenforced DEPOSIT cannot be used to create or checkpoint a channel on-chain, because OPERATE requires zero change in user net flow relative to the last enforced state +- 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 +- **Off-chain transfer routing** — when a user sends funds off-chain to another party, the node must countersign both the sender's state (decreasing their allocation) and the receiver's credit state (increasing theirs); the on-chain contract cannot enforce atomicity between two independent channel updates. A malicious node could apply the sender's state while withholding the receiver's credit, capturing the transferred funds. Users must trust the node to faithfully execute both legs of every off-chain transfer. +- **Signature validator registry** — the node operator controls which additional signature validators are registered on the ChannelHub contract. A malicious or compromised node could register a validator that approves forged user signatures, then use it to create channels or close them without the user's knowledge. A 1-day activation delay (`VALIDATOR_ACTIVATION_DELAY`) creates an observable window before any newly registered validator can be used. Users MUST monitor the `ValidatorRegistered` event on the ChannelHub contract and SHOULD revoke all ERC20 approvals granted to ChannelHub immediately upon detecting an unexpected registration. Once registered, a validator cannot be deactivated — the 1-day window is the entire response budget. Users SHOULD avoid granting large standing ERC20 approvals to ChannelHub to cap worst-case exposure. + +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 or have acknowledged design trade-offs: + +- 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 +- Session key off-chain scope enforcement does not apply to direct receive-state acknowledgement. Session key expiration and asset-scope restrictions are enforced by the Nitronode off-chain only; the `SessionKeyValidator` contract validates cryptographic signatures alone. A party holding a session key — even one that has expired, been revoked, or been retired — can bypass the `acknowledge` endpoint, manually sign a pending node-issued receive state, and submit it directly to the contract. This is accepted: receive states exclusively increase the user's allocation and cannot redirect funds away from the user, so out-of-scope acknowledgement carries no financial risk and preserves a recovery path when the node is unavailable. + +## 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..1835b73e8 --- /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 state updates on their behalf within an authorization scope. Session key authorization MUST be associated with the same address as the channel's user or node participant. The authorization scope is defined by an extensible metadata structure (see [Cryptography — Session Key Authorization](cryptography.md#session-key-authorization)). Participants SHOULD treat session-key compromise as equivalent to control of the participant's authority within the scope they issued, for the duration of the validity window. + +### 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 <> Nitronode 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/clearnode/chart/.helmignore b/faucet-app/chart/.helmignore similarity index 100% rename from clearnode/chart/.helmignore rename to faucet-app/chart/.helmignore diff --git a/faucet-app/chart/Chart.yaml b/faucet-app/chart/Chart.yaml new file mode 100644 index 000000000..b2a6968d7 --- /dev/null +++ b/faucet-app/chart/Chart.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v2 +description: Faucet App Helm chart +name: faucet-app +version: 1.0.0 +appVersion: "1.3.0" diff --git a/faucet-app/chart/config/sandbox-v1/faucet.yaml.gotmpl b/faucet-app/chart/config/sandbox-v1/faucet.yaml.gotmpl new file mode 100644 index 000000000..bea2148a2 --- /dev/null +++ b/faucet-app/chart/config/sandbox-v1/faucet.yaml.gotmpl @@ -0,0 +1,66 @@ +config: + args: ["faucet-server"] + logLevel: info + # Faucet talks to the nitronode release in the same env, intra-cluster. + # Intra-cluster URL — bypasses nitronode ingress, so use the app's native /ws + # path (nitronode-internal listener). The /v1/ws prefix only exists on the + # public ingress, which rewrites it to /ws before forwarding. + nitronodeWsUrl: "ws://nitronode:7824/ws" + token: + symbol: yusd + tipAmount: 10 + minTransferCount: 5 + cooldownPeriod: 24h + # Per-IP flood protection comes from the ingress annotations below + # (limit-connections / limit-rps). The app keeps only the per-wallet + # cooldown so a single IP can still drip multiple wallets per day — + # accepted because each wallet is one-shot and MIN_TRANSFER_COUNT + # guards the faucet's runway separately. + ipRateLimitEnabled: false + trustedProxies: "" + envSecret: "" +# ownerAddress: "0xccdaC3e7BB78572a5f67A3AA85188FAA8F35549C" + +service: + http: + enabled: true + port: 8080 + # External path. NGINX rewrite (below) strips the prefix so the app sees + # the original request path (e.g. /requestTokens). + path: /v1/faucet-app(/|$)(.*) + +metrics: + enabled: false + +replicaCount: 1 +resources: + requests: { cpu: 100m, memory: 128Mi, ephemeral-storage: 128Mi } + limits: { cpu: 100m, memory: 128Mi, ephemeral-storage: 128Mi } + +autoscaling: + enabled: false + +networking: + # Same host as nitronode-sandbox; routed by path. cert-manager reuses the + # TLS secret already provisioned by the nitronode ingress on this host. + externalHostname: nitronode-sandbox.yellow.org + tlsClusterIssuer: zerossl-prod + gateway: + enabled: false + ingress: + enabled: true + className: nginx + tls: + enabled: true + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 + # ── Per-IP flood protection (NGINX-level) ──────────────────────────── + # Replaces the app-level per-IP bucket. Effective only when ingress-nginx + # sees real client IPs; sandbox-v1 has no Cloudflare in front, so source + # IP comes from the GCP LB — verify externalTrafficPolicy=Local on the + # ingress-nginx Service before relying on these. Lower than nitronode's + # numbers because faucet is rare-write. + nginx.ingress.kubernetes.io/limit-connections: "10" # concurrent / IP + nginx.ingress.kubernetes.io/limit-rps: "5" # new conns/s / IP + nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" diff --git a/faucet-app/chart/config/sandbox-v1/secrets.yaml b/faucet-app/chart/config/sandbox-v1/secrets.yaml new file mode 100644 index 000000000..9a2d53d50 --- /dev/null +++ b/faucet-app/chart/config/sandbox-v1/secrets.yaml @@ -0,0 +1,12 @@ +config: + secretEnvs: + OWNER_PRIVATE_KEY: ENC[AES256_GCM,data:c54AOaPC1/b2MO/MK1h+i47HTCV9u7AF2zR6w/IDxYSc0r/NMmRbOauifKWikRFnDi4CXbjykqSx6ed60in9gg==,iv:fvY9/CguEUdAqjmH93ujsJZtOUgamfbNV81eh2AKttg=,tag:cLVQUWovRQqo3S7M+Kb9gg==,type:str] +sops: + gcp_kms: + - resource_id: projects/ynet-stage/locations/global/keyRings/sops/cryptoKeys/sops-uat-key + created_at: "2026-05-25T11:37:38Z" + enc: CiQAMMK76efRl7+42AnG9EjAWWg4BKgwkCTPh5zxNLMdKu+LC1cSSQC9etkICsswCHabKY9Uy4/b1eIrSUqTQWb5ISp3iHd/RMrQaBw3cMCI2OKvZgzvPyHRWv+dHfAet51VZSBVS57cw1T6yWNlQsk= + lastmodified: "2026-05-25T11:38:27Z" + mac: ENC[AES256_GCM,data:Loz3ojmAzqKDwKNn4DnuFRSDKG9okWnET8vlYwdMSTjt/KI+1xjO2JQOYJ1ekTvPilHYAwI/hUWSi+Yu7wR/NqG9ktNd/DDVpvo7DjlTUlAQgr+Cd2hXYaWtRRh7m6iT+NqcJxIA0CNQVwmlUK6XIEj/hh4bL43UPoyjK2+wxfY=,iv:iPNnuhRef+UlzT3S4qujX1x/1CMQDMg4tj0rnpryt24=,tag:pBmnKLSpo/D68RhV6NXYQw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 diff --git a/faucet-app/chart/config/stress-v1/faucet.yaml.gotmpl b/faucet-app/chart/config/stress-v1/faucet.yaml.gotmpl new file mode 100644 index 000000000..5bde8cd6b --- /dev/null +++ b/faucet-app/chart/config/stress-v1/faucet.yaml.gotmpl @@ -0,0 +1,59 @@ +config: + args: ["faucet-server"] + logLevel: info + # Faucet talks to the nitronode release in the same env, intra-cluster. + # Intra-cluster URL — bypasses nitronode ingress, so use the app's native /ws + # path (nitronode-internal listener). The /v1/ws prefix only exists on the + # public ingress, which rewrites it to /ws before forwarding. + nitronodeWsUrl: "ws://nitronode:7824/ws" + token: + symbol: yusd + tipAmount: 10 + minTransferCount: 5 + cooldownPeriod: 24h + # Per-IP flood protection lives on the ingress (see annotations below). + ipRateLimitEnabled: false + trustedProxies: "" + envSecret: "" +# ownerAddress: "0x5FF0Fdb7A3Cda14387d5ea5A550381d6114D1177" + +service: + http: + enabled: true + port: 8080 + # External path. NGINX rewrite (below) strips the prefix so the app sees + # the original request path (e.g. /requestTokens). + path: /v1/faucet-app(/|$)(.*) + +metrics: + enabled: false + +replicaCount: 1 +resources: + requests: { cpu: 100m, memory: 128Mi, ephemeral-storage: 128Mi } + limits: { cpu: 100m, memory: 128Mi, ephemeral-storage: 128Mi } + +autoscaling: + enabled: false + +networking: + # Same host as nitronode-stress; routed by path. cert-manager reuses the + # TLS secret already provisioned by the nitronode ingress on this host. + externalHostname: nitronode-stress.yellow.org + tlsClusterIssuer: zerossl-prod + gateway: + enabled: false + ingress: + enabled: true + className: nginx + tls: + enabled: true + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 + # ── Per-IP flood protection (NGINX-level) ──────────────────────────── + # Replaces the app-level per-IP bucket; see sandbox-v1 for caveats on + # source-IP propagation. Lower numbers because faucet is rare-write. + nginx.ingress.kubernetes.io/limit-connections: "10" + nginx.ingress.kubernetes.io/limit-rps: "5" + nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" diff --git a/faucet-app/chart/config/stress-v1/secrets.yaml b/faucet-app/chart/config/stress-v1/secrets.yaml new file mode 100644 index 000000000..2c745a930 --- /dev/null +++ b/faucet-app/chart/config/stress-v1/secrets.yaml @@ -0,0 +1,12 @@ +config: + secretEnvs: + OWNER_PRIVATE_KEY: ENC[AES256_GCM,data:YcDPaepdZ4g9JRq16aB/U2cMZ/4PvTydrkceNyjZ02lJBztyWUAtP00yuEgBT//n+HW6ZmTaHnCN+CdOIkZokw==,iv:lGsBd2Pa/5kx87YOlpVy5Wd87X0y89R6eAe/qrgMCLU=,tag:3uNT7i03jkRdg6B0fVp1Xg==,type:str] +sops: + gcp_kms: + - resource_id: projects/ynet-stage/locations/global/keyRings/sops/cryptoKeys/sops-uat-key + created_at: "2026-05-25T11:37:43Z" + enc: CiQAMMK76Uhrh3w/JAx38ps76DFbaO+J/OL438qmIPgtiEodLtcSSQC9etkIsBCWeg9HtapqEY2h2aT+6j5thcihCYuABKlBdzhAb5+JVFrPkOSRWzfjxcRjCqPY9jMh7EQcNs9ly76GzH8CExQTUbw= + lastmodified: "2026-05-25T11:38:36Z" + mac: ENC[AES256_GCM,data:e4hRk9RmrKtBWA7FvUrsb5UQEWNNfSq2hHEcxT6sNlj22djKAZgV+XI9SR+vQhX7LGrelhvFd2TdnTzIwG7GmVDshfpDee3YnkJBIUHs+l2mYLw6L36nlwzSwdU8AKCqxbOVvi42SaiNh+uPyqZYTvJVMBM9IFPbnbw5ZjFV2A8=,iv:8e3aaK1R5e1ONarRfUUpjCnDpXu8u/S1oj4XvcJn1ic=,tag:GbPJECvitkVF0WqyHf5G7g==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 diff --git a/faucet-app/chart/templates/deployment.yaml b/faucet-app/chart/templates/deployment.yaml new file mode 100644 index 000000000..5c48c3004 --- /dev/null +++ b/faucet-app/chart/templates/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "faucet-app.common.fullname" . }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} +spec: + {{- include "faucet-app.component.replicaCount" . | nindent 2 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + {{- include "faucet-app.common.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "faucet-app.component.metricsAnnotations" .Values.metrics | nindent 8 }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + labels: + {{- include "faucet-app.common.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.serviceAccount }} + serviceAccountName: {{ . }} + {{- end }} + containers: + - name: api + args: {{ .Values.config.args | toYaml | nindent 10 }} + image: {{ include "faucet-app.component.image" .Values.image }} + imagePullPolicy: IfNotPresent + env: + {{- include "faucet-app.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 "faucet-app.common.fullname" . }}-secret-env + {{- end }} + {{- end }} + {{- include "faucet-app.component.ports" .Values.service | nindent 10 }} + {{- include "faucet-app.component.resources" .Values.resources | nindent 10 }} + {{- include "faucet-app.component.probes" . | nindent 10 }} + {{- include "faucet-app.common.imagePullSecrets" . | nindent 6 }} + {{- include "faucet-app.common.nodeSelectorLabels" . | nindent 6 }} + {{- include "faucet-app.common.affinity" . | nindent 6 }} + {{- include "faucet-app.common.tolerations" . | nindent 6 }} diff --git a/faucet-app/chart/templates/gateway.yaml b/faucet-app/chart/templates/gateway.yaml new file mode 100644 index 000000000..48fee0bf6 --- /dev/null +++ b/faucet-app/chart/templates/gateway.yaml @@ -0,0 +1,31 @@ +{{- if .Values.networking.gateway.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ include "faucet-app.common.fullname" . }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} + annotations: + cert-manager.io/cluster-issuer: {{ .Values.networking.tlsClusterIssuer }} + # FIXME: This annotation doesn't work + {{- if .Values.networking.gateway.ipAddressName }} + gateway.envoyproxy.io/service-annotations: | + networking.gke.io/load-balancer-ip-addresses: "{{ .Values.networking.gateway.ipAddressName }}" + {{- end }} +spec: + gatewayClassName: {{ .Values.networking.gateway.className }} + listeners: + - name: http + protocol: HTTP + hostname: {{ .Values.networking.externalHostname }} + port: 80 + - name: https + protocol: HTTPS + hostname: {{ .Values.networking.externalHostname }} + port: 443 + tls: + mode: Terminate + certificateRefs: + - kind: Secret + name: {{ printf "%s-tls" (.Values.networking.externalHostname | replace "." "-") }} +{{- end }} diff --git a/faucet-app/chart/templates/helpers/_common.tpl b/faucet-app/chart/templates/helpers/_common.tpl new file mode 100644 index 000000000..ab2ddae35 --- /dev/null +++ b/faucet-app/chart/templates/helpers/_common.tpl @@ -0,0 +1,124 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the component. +*/}} +{{- define "faucet-app.common.name" -}} +{{- .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully common name. +If release name contains chart name it will be used as a full name. +*/}} +{{- define "faucet-app.common.fullname" -}} +{{- if .Values.prefixOverride }} +{{- printf "%s-%s" .Values.prefixOverride .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- if contains .Chart.Name .Release.Name }} +{{- print .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Common Selector labels +*/}} +{{- define "faucet-app.common.selectorLabels" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "faucet-app.common.labels" -}} +helm.sh/chart: {{ include "faucet-app.common.chart" . }} +{{ include "faucet-app.common.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.extraLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "faucet-app.common.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Returns common image pull secrets +*/}} +{{- define "faucet-app.common.imagePullSecrets" -}} +{{- with .Values.imagePullSecret }} +imagePullSecrets: +- name: {{ . }} +{{- end }} +{{- end }} + +{{/* +Returns common environment variables +*/}} +{{- define "faucet-app.common.env" -}} +- name: LOG_LEVEL + value: {{ .Values.config.logLevel | quote }} +- name: SERVER_PORT + value: {{ .Values.service.http.internalPort | default "8080" | print | quote }} +- name: NITRONODE_URL + value: {{ .Values.config.nitronodeWsUrl | print }} +- name: TOKEN_SYMBOL + value: {{ .Values.config.token.symbol | print }} +- name: STANDARD_TIP_AMOUNT + value: {{ .Values.config.token.tipAmount | print | quote }} +- name: MIN_TRANSFER_COUNT + value: {{ .Values.config.minTransferCount | print | quote }} +- name: COOLDOWN_PERIOD + value: {{ .Values.config.cooldownPeriod | print | quote }} +- name: IP_RATE_LIMIT_ENABLED + value: {{ .Values.config.ipRateLimitEnabled | print | quote }} +{{- with .Values.config.trustedProxies }} +- name: TRUSTED_PROXIES + value: {{ . | print | quote }} +{{- end }} +{{- range $key, $value := .Values.config.extraEnvs }} +- name: {{ $key | upper }} + value: {{ $value | print | quote }} +{{- end }} +{{- end }} + +{{/* +Returns common node selector labels +*/}} +{{- define "faucet-app.common.nodeSelectorLabels" -}} +{{- with .Values.nodeSelector }} +nodeSelector: + {{ toYaml . | nindent 2 }} +{{- end }} +{{- end }} + +{{/* +Returns common tolerations +*/}} +{{- define "faucet-app.common.tolerations" -}} +{{- with .Values.tolerations }} +tolerations: +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Returns common pod's affinity +*/}} +{{- define "faucet-app.common.affinity" -}} +{{- with .Values.affinity }} +affinity: + {{ toYaml . | nindent 2 }} +{{- end }} +{{- end }} diff --git a/faucet-app/chart/templates/helpers/_component.tpl b/faucet-app/chart/templates/helpers/_component.tpl new file mode 100644 index 000000000..ff4b48d8d --- /dev/null +++ b/faucet-app/chart/templates/helpers/_component.tpl @@ -0,0 +1,78 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Returns Prometheus metrics' annotations depending on input Values +*/}} +{{- define "faucet-app.component.metricsAnnotations" -}} +prometheus.io/scrape: {{ default false .enabled | print | quote }} +prometheus.io/port: {{ default "4242" .port | print | quote }} +prometheus.io/path: {{ default "/metrics" .endpoint | print | quote }} +{{- end }} + +{{/* +Returns replica count depending on component and HPA settings +*/}} +{{- define "faucet-app.component.replicaCount" -}} +{{- if not (and .autoscaling .autoscaling.enabled) }} +replicas: {{ .replicaCount }} +{{- end }} +{{- end }} + +{{/* +Returns full docker image name +*/}} +{{- define "faucet-app.component.image" -}} +{{ printf "%s:%s" (print .repository) (print .tag) }} +{{- end }} + +{{/* +Returns container ports configuration depending on input service +*/}} +{{- define "faucet-app.component.ports" -}} +{{- if .http.enabled }} +ports: +{{- with .http }} +- name: http + containerPort: {{ default .port .internalPort }} + protocol: TCP +{{- end }} +{{- end }} +{{- end }} + +{{/* +Returns component's resource consumption +*/}} +{{- define "faucet-app.component.resources" -}} +resources: + requests: + cpu: {{ default "100m" .requests.cpu }} + memory: {{ default "128Mi" .requests.memory }} + ephemeral-storage: {{ default "100Mi" .requests.memory }} + limits: + cpu: {{ default "100m" .requests.cpu }} + memory: {{ default "128Mi" .requests.memory }} + ephemeral-storage: {{ default "100Mi" .requests.memory }} +{{- end }} + +{{/* +Returns component's probes +*/}} +{{- define "faucet-app.component.probes" -}} +{{- $port := default .Values.service.http.port .Values.service.http.internalPort }} +{{- range $name, $probe := .Values.probes }} +{{- if $probe.enabled }} +{{ printf "%sProbe" $name }}: + {{- if eq $probe.type "http" }} + httpGet: + port: {{ $port }} + path: {{ default "/health" $probe.endpoint }} + {{- else }} + tcpSocket: + port: {{ $port }} + {{- end }} + initialDelaySeconds: {{ default 5 $probe.initialDelaySeconds }} + timeoutSeconds: {{ default 10 $probe.timeoutSeconds }} + periodSeconds: {{ default 10 $probe.periodSeconds }} +{{- end }} +{{- end }} +{{- end }} diff --git a/faucet-app/chart/templates/helpers/_hpa.tpl b/faucet-app/chart/templates/helpers/_hpa.tpl new file mode 100644 index 000000000..80006892c --- /dev/null +++ b/faucet-app/chart/templates/helpers/_hpa.tpl @@ -0,0 +1,24 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Returns HorizontalPodAutoscaler API version depending on K8s cluster version +*/}} +{{- define "faucet-app.hpa.apiVersion" -}} +{{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.Version -}} +autoscaling/v2 +{{- else -}} +autoscaling/v2beta2 +{{- end }} +{{- end }} + +{{/* +Returns HorizontalPodAutoscaler resource target utilization depending on K8s cluster version +*/}} +{{- define "faucet-app.hpa.targetUtilization" -}} +{{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.Version -}} +target: + type: Utilization + averageUtilization: {{ .averageUtilization }} +{{- else -}} +targetAverageUtilization: {{ .averageUtilization }} +{{- end }} +{{- end }} diff --git a/faucet-app/chart/templates/helpers/_ingress.tpl b/faucet-app/chart/templates/helpers/_ingress.tpl new file mode 100644 index 000000000..0bf1ec4fa --- /dev/null +++ b/faucet-app/chart/templates/helpers/_ingress.tpl @@ -0,0 +1,67 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Returns Ingress API version depending on K8s cluster version +*/}} +{{- define "faucet-app.ingress.apiVersion" -}} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.Version -}} +networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.Version -}} +networking.k8s.io/v1beta1 +{{- else -}} +extensions/v1beta1 +{{- end }} +{{- end }} + +{{/* +Returns default Ingress annotations +*/}} +{{- define "faucet-app.ingress.annotations" -}} +kubernetes.io/ingress.class: {{ default "nginx" .Values.networking.ingress.className }} +{{- if .Values.networking.ingress.tls.enabled }} +kubernetes.io/tls-acme: "true" +cert-manager.io/cluster-issuer: {{ .Values.networking.tlsClusterIssuer }} +nginx.ingress.kubernetes.io/ssl-redirect: "true" +{{- end }} +{{- if .Values.networking.ingress.grpc }} +nginx.ingress.kubernetes.io/backend-protocol: "GRPC" +{{- end }} +{{- with .Values.networking.ingress.annotations }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Returns Ingress TLS configuration +*/}} +{{- define "faucet-app.ingress.tls" -}} +{{- if .Values.networking.ingress.tls.enabled }} +tls: + - secretName: "{{ .Values.networking.externalHostname | replace "." "-" }}-tls" + hosts: + - "{{ .Values.networking.externalHostname }}" +{{- end }} +{{- end }} + +{{/* +Returns Ingress host path configuration +*/}} +{{- define "faucet-app.ingress.httpPath" -}} +{{- $http := .Values.service.http }} +- path: {{ $http.path }} + {{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.Version }} + pathType: ImplementationSpecific + {{- end }} + backend: + {{ $svcName := include "faucet-app.common.fullname" . }} + {{ $svcPort := default $http.port $http.internalPort }} + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.Version }} + service: + name: {{ $svcName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $svcName }} + servicePort: {{ $svcPort }} + {{- end }} +{{- end }} diff --git a/faucet-app/chart/templates/http-route-redirect.yaml b/faucet-app/chart/templates/http-route-redirect.yaml new file mode 100644 index 000000000..cfe3af2cb --- /dev/null +++ b/faucet-app/chart/templates/http-route-redirect.yaml @@ -0,0 +1,25 @@ +{{- if .Values.networking.gateway.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ printf "%s-http-redirect" (include "faucet-app.common.fullname" .) }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: {{ .Release.Namespace }} + name: {{ include "faucet-app.common.fullname" . }} + port: 80 + sectionName: http + hostnames: + - {{ .Values.networking.externalHostname }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + hostname: {{ .Values.networking.externalHostname }} +{{- end }} diff --git a/faucet-app/chart/templates/http-route.yaml b/faucet-app/chart/templates/http-route.yaml new file mode 100644 index 000000000..587eb211b --- /dev/null +++ b/faucet-app/chart/templates/http-route.yaml @@ -0,0 +1,27 @@ +{{- $http := .Values.service.http }} +{{- if and .Values.networking.gateway.enabled $http.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "faucet-app.common.fullname" . }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: {{ .Release.Namespace }} + name: {{ include "faucet-app.common.fullname" . }} + port: 443 + sectionName: https + hostnames: + - {{ .Values.networking.externalHostname }} + rules: + - matches: + - path: + type: PathPrefix + value: {{ $http.path }} + backendRefs: + - name: {{ include "faucet-app.common.fullname" . }} + port: {{ default $http.port $http.internalPort }} +{{- end }} diff --git a/faucet-app/chart/templates/ingress.yaml b/faucet-app/chart/templates/ingress.yaml new file mode 100644 index 000000000..7824ff2c9 --- /dev/null +++ b/faucet-app/chart/templates/ingress.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.networking.ingress.enabled .Values.service.http.enabled }} +apiVersion: {{ include "faucet-app.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "faucet-app.common.fullname" . }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} + annotations: + {{- include "faucet-app.ingress.annotations" . | nindent 4 }} +spec: + rules: + - host: {{ .Values.networking.externalHostname }} + http: + paths: + {{- include "faucet-app.ingress.httpPath" . | nindent 10 }} + {{- include "faucet-app.ingress.tls" . | nindent 2 }} +{{- end }} diff --git a/faucet-app/chart/templates/podmonitoring.yaml b/faucet-app/chart/templates/podmonitoring.yaml new file mode 100644 index 000000000..31ac7dbdf --- /dev/null +++ b/faucet-app/chart/templates/podmonitoring.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.metrics.enabled .Values.metrics.podmonitoring.enabled }} +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring +metadata: + name: {{ include "faucet-app.common.fullname" . }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "faucet-app.common.selectorLabels" . | nindent 6 }} + endpoints: + - port: {{ .Values.metrics.port }} + path: {{ .Values.metrics.endpoint }} + interval: {{ .Values.metrics.scrapeInterval }} +{{- end }} diff --git a/faucet-app/chart/templates/secret.yaml b/faucet-app/chart/templates/secret.yaml new file mode 100644 index 000000000..2bb5664e7 --- /dev/null +++ b/faucet-app/chart/templates/secret.yaml @@ -0,0 +1,16 @@ +{{- if .Values.config.secretEnvs }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "faucet-app.common.fullname" . }}-secret-env + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-weight": "-1" +data: +{{- range $key, $value := .Values.config.secretEnvs }} + {{- $key | nindent 2 }}: {{ $value | print | b64enc }} +{{- end }} +{{- end }} diff --git a/faucet-app/chart/templates/service.yaml b/faucet-app/chart/templates/service.yaml new file mode 100644 index 000000000..04854f913 --- /dev/null +++ b/faucet-app/chart/templates/service.yaml @@ -0,0 +1,20 @@ +{{- $svc := .Values.service }} +{{- if $svc.http.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "faucet-app.common.fullname" . }} + labels: + {{- include "faucet-app.common.labels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + {{- with $svc.http }} + - name: http + port: {{ default .port .externalPort }} + targetPort: {{ default .port .internalPort }} + protocol: TCP + {{- end }} + selector: + {{- include "faucet-app.common.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/faucet-app/chart/values.yaml b/faucet-app/chart/values.yaml new file mode 100644 index 000000000..750f28b66 --- /dev/null +++ b/faucet-app/chart/values.yaml @@ -0,0 +1,153 @@ +# -- Override the full name +fullnameOverride: "" + +config: + # -- List of arguments to pass to the container. Binary is on PATH at + # /bin/faucet-server (see Dockerfile CMD); the chart only needs to override + # this when running with extra flags. + args: ["faucet-server"] + # -- pkg/log level (debug, info, warn, error, fatal). LOG_FORMAT / LOG_OUTPUT + # are not surfaced as chart values; pkg/log defaults to console + stderr, + # matching the nitronode chart. Override via config.extraEnvs if needed. + logLevel: info + # -- Nitronode WebSocket URL + nitronodeWsUrl: "ws://nitronode.example.svc.cluster.local:7824/ws" + token: + # -- Token symbol inside the Nitronode network + symbol: usdc + # -- The amount of tokens to tip per request + tipAmount: 10 + # -- Minimum number of transfers the server should have a balance for to operate + minTransferCount: 5 + # -- Cooldown between requests per wallet/IP (Go duration, e.g. 24h, 1h30m). + # Applies to both the per-address and per-IP buckets when both are active. + cooldownPeriod: 24h + # -- Whether the per-IP rate-limit bucket is active. Set `false` when + # ingress-level limits (limit-rps, limit-connections) handle per-IP flood + # protection — see networking.ingress.annotations in the per-env values. + # Leave `true` only if the faucet is exposed without a fronting ingress. + ipRateLimitEnabled: true + # -- Comma-separated trusted proxy IPs/CIDRs; empty means direct exposure only + trustedProxies: "" + # -- Additional environment variables as key-value pairs + extraEnvs: {} + # KEY: VALUE + # -- Additional environment variables to be stored in a secret + secretEnvs: {} + # KEY: VALUE + # -- Name of the secret containing environment variables + envSecret: "" + +# -- Number of replicas +replicaCount: 1 + +image: + # -- Docker image repository + repository: ghcr.io/layer-3/nitrolite/faucet-app + # -- Docker image tag (set via helmfile/CI) + tag: "" + +service: + http: + # -- Enable HTTP service + enabled: true + # -- HTTP service port + port: 8000 + # -- HTTP service path + path: / + +metrics: + # -- Enable Prometheus metrics + enabled: true + podmonitoring: + # -- Enable PodMonitoring for Managed Prometheus + enabled: false + # -- Metrics port + port: 4242 + # -- Metrics endpoint path + endpoint: "/metrics" + # -- Metrics scrape interval + scrapeInterval: 30s + +probes: + liveness: + # -- Enable liveness probe + enabled: false + # -- Liveness probe type (http, tcp) + type: tcp + readiness: + # -- Enable readiness probe + enabled: false + # -- Readiness probe type (http, tcp) + type: tcp + +resources: + # -- Resource limits + limits: {} + # cpu: 100m + # memory: 256Mi + # ephemeral-storage: 100Mi + # -- Resource requests + requests: {} + # cpu: 100m + # memory: 256Mi + # ephemeral-storage: 100Mi + +# -- Service account name +serviceAccount: "" + +autoscaling: + # -- Enable autoscaling + enabled: false + # -- Minimum number of replicas + minReplicas: 2 + # -- Maximum number of replicas + maxReplicas: 100 + # -- Target CPU utilization + targetCPUUtilizationPercentage: 80 + # -- Target memory utilization + targetMemoryUtilizationPercentage: 80 + +networking: + # -- TLS cluster issuer + tlsClusterIssuer: zerossl-prod + # -- External hostname for the gateway + externalHostname: faucet.example.com + + gateway: + # -- Enable API gateway + enabled: true + # -- Gateway class name + className: envoy-gateway + # -- GKE static IP address name (GKE only) + ipAddressName: "" + + ingress: + # -- Enable ingress + enabled: false + # -- Ingress class name + className: nginx + # -- Ingress annotations + annotations: {} + # -- Enable GRPC for ingress + grpc: false + tls: + # -- Enable TLS for ingress + enabled: false + +# -- Image pull secret name. Default refers to the Secret created by the +# co-deployed nitronode release in the same namespace (faucet `needs:` +# nitronode in helmfile, so it always exists by the time faucet pulls). +imagePullSecret: nitronode-ghcr-pull + +# -- Node selector +nodeSelector: {} + +# -- Tolerations +tolerations: [] + +# -- Affinity settings +affinity: {} + +# -- Additional labels to add to all resources +extraLabels: {} diff --git a/faucet-app/server/.env.example b/faucet-app/server/.env.example new file mode 100644 index 000000000..5b4b9d796 --- /dev/null +++ b/faucet-app/server/.env.example @@ -0,0 +1,63 @@ +# ============================================================================= +# Nitrolite Faucet Server Configuration +# ============================================================================= +# Copy this file to .env and update the values according to your setup +# +# You can also set these as environment variables instead of using .env file +# Environment variables take precedence over .env file values +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Server Configuration +# ----------------------------------------------------------------------------- +# HTTP server port +SERVER_PORT=8080 + +# ----------------------------------------------------------------------------- +# Nitronode Configuration +# ----------------------------------------------------------------------------- +# Private key for faucet owner wallet (without 0x prefix) +# REQUIRED: Used for EIP-712 authentication with Nitronode +OWNER_PRIVATE_KEY=your_owner_private_key_here_without_0x_prefix + +# Cooldown between requests per wallet/IP (Go duration format) +# Optional: default is 24h +# COOLDOWN_PERIOD=your_cooldown_period_here (e.g., 24h, 1h, 30m) + +# Whether the per-IP rate-limit bucket is active. +# Set false when the fronting ingress enforces per-IP limits — without +# TRUSTED_PROXIES the app sees the proxy's IP for every request and the +# per-IP bucket collapses to a single global slot. +# Optional: default is true. +# IP_RATE_LIMIT_ENABLED=true + +# Nitronode WebSocket URL +# REQUIRED: The WebSocket endpoint for your Nitronode instance +NITRONODE_URL=wss://nitronode.example.com/ws + +# Token symbol to distribute +# REQUIRED: The symbol of the token to distribute (must be supported by Nitronode) +TOKEN_SYMBOL=usdc + +# Default amount to send per request +# REQUIRED: Amount in decimal format (e.g., 1.0 = 1 USDC, 0.5 = 0.5 ETH) +STANDARD_TIP_AMOUNT=10.0 + +# Minimum number of transfers the server should have a balance for to operate +# REQUIRED: Integer value (e.g., 5) +MIN_TRANSFER_COUNT=5 + +# ----------------------------------------------------------------------------- +# Logging Configuration (pkg/log) +# ----------------------------------------------------------------------------- +# Logging level (debug, info, warn, error, fatal) +# Default: info +LOG_LEVEL=info + +# Logging format (console, logfmt, json) +# Default: console (use "json" in production) +LOG_FORMAT=console + +# Logging output (stderr, stdout, or file path) +# Default: stderr +LOG_OUTPUT=stderr diff --git a/faucet-app/server/.gitignore b/faucet-app/server/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/faucet-app/server/.gitignore @@ -0,0 +1 @@ +.env diff --git a/faucet-app/server/Dockerfile b/faucet-app/server/Dockerfile new file mode 100644 index 000000000..79171cc24 --- /dev/null +++ b/faucet-app/server/Dockerfile @@ -0,0 +1,28 @@ +FROM golang:1.25-alpine AS builder + +WORKDIR /build + +# Copy root module files and pre-download dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy full source tree +COPY . . + +# Build the faucet server binary +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /build/bin/faucet-server ./faucet-app/server + +FROM alpine:3.23.3 + +RUN apk --no-cache add ca-certificates + +RUN addgroup -g 1001 -S faucet +RUN adduser -S faucet -u 1001 -G faucet + +USER faucet + +COPY --from=builder /build/bin/faucet-server /bin/faucet-server + +EXPOSE 8080 + +CMD ["faucet-server"] diff --git a/faucet-app/server/README.md b/faucet-app/server/README.md new file mode 100644 index 000000000..1540c441e --- /dev/null +++ b/faucet-app/server/README.md @@ -0,0 +1,173 @@ +# Nitrolite Faucet Server + +A Go-based faucet server that distributes tokens through the Nitronode network using WebSocket connections. + +## Features + +- **Nitrolite SDK Integration**: Uses the local `github.com/layer-3/nitrolite` SDK for Nitronode communication +- **Ethereum Wallet Integration**: Uses ECDSA private key for signing channel states and transactions +- **RESTful API**: Simple HTTP endpoints for token requests +- **Structured Logging**: JSON-formatted logs with configurable levels +- **Graceful Shutdown**: Proper cleanup of connections and resources +- **Address Validation**: Validates Ethereum addresses before processing requests + +## Architecture + +The application is structured into several packages: + +- `internal/config`: Configuration management with environment variables +- `internal/logger`: Structured logging with logrus +- `internal/nitronode`: Thin wrapper around the Nitrolite SDK client +- `internal/server`: HTTP server with Gin framework + +### Nitronode Client + +The `internal/nitronode` package wraps the Nitrolite SDK's `sdk.Client`. Connection and message signing are handled internally by the SDK — no manual WebSocket management is required. + +## Quick Start + +1. **Setup**: + + ```bash + cd faucet-app/server + go mod tidy + ``` + +2. **Configure environment**: + + ```bash + cp .env.example .env + # Edit .env with your configuration + ``` + +3. **Run the server**: + + ```bash + go run main.go + ``` + +## Configuration + +The application uses [cleanenv](https://github.com/ilyakaznacheev/cleanenv) for configuration management. Configuration can be provided via: + +1. **`.env` file** in the current directory +2. **Environment variables** (used when `.env` is absent) + +Set the following environment variables: + +| Variable | Required | Default | Description | Example | +|----------|----------|---------|-------------|---------| +| `SERVER_PORT` | No | `8080` | HTTP server port | `8080` | +| `OWNER_PRIVATE_KEY` | **Yes** | - | Owner private key (without 0x prefix) — signs channel states and transfers | `abcdef123...` | +| `NITRONODE_URL` | **Yes** | - | Nitronode WebSocket URL | `wss://nitronode.example.com/ws` | +| `TOKEN_SYMBOL` | **Yes** | - | Token symbol to distribute | `usdc` | +| `STANDARD_TIP_AMOUNT` | **Yes** | - | Amount to send per request (decimal format) | `10.0` | +| `MIN_TRANSFER_COUNT` | **Yes** | - | Minimum number of transfers the server should have balance for | `5` | +| `COOLDOWN_PERIOD` | **Yes** | - | Cooldown between requests per wallet/IP (Go duration format) | `24h` | +| `TRUSTED_PROXIES` | No | `""` | Comma-separated trusted proxy IPs; empty means direct exposure only | `10.0.0.1,10.0.0.2` | +| `LOG_LEVEL` | No | `info` | Logging level (debug/info/warn/error) | `info` | + +> **Note on `TRUSTED_PROXIES`:** If the faucet is deployed behind an ingress or load balancer, set this to the proxy IP(s). Without it, `c.ClientIP()` returns the proxy address and all requests share one IP rate-limit bucket. + +## API Endpoints + +### `POST /requestTokens` + +Request tokens for an Ethereum address. + +**Request body:** +```json +{ "userAddress": "0x..." } +``` + +**Success response (200):** +```json +{ + "success": true, + "message": "Tokens sent successfully", + "txId": "...", + "amount": "10", + "asset": "usdc", + "destination": "0x..." +} +``` + +**Error responses:** +- `400` — Invalid address or request format +- `429` — Rate limit exceeded +- `500` — Transfer failed +- `503` — Nitronode unavailable or balance insufficient + +### `GET /info` + +Returns server metadata. + +## WebSocket Connection Management + +The Nitrolite SDK maintains a persistent WebSocket connection with Nitronode: + +- **Connection**: Established on startup inside `nitronode.NewClient()`; no separate connect/auth step is needed +- **Authentication**: Handled internally by the SDK +- **Reconnection**: On each request, `EnsureConnected()` detects a lost connection (via `WaitCh()`) and reconnects with exponential backoff (3 attempts, 300 ms → 600 ms → 2 s) +- **Post-reconnect ping**: Each reconnect attempt is validated with a `Ping` before the new client is accepted +- **Message Handling**: Fully managed by the SDK's internal RPC layer + +## Startup Log Example + +``` +{"level":"info","msg":"Starting Nitrolite Faucet Server","time":"..."} +{"level":"info","msg":"Configuration loaded: Server port=8080, Nitronode URL=wss://nitronode.example.com","time":"..."} +{"level":"debug","msg":"Token 'usdc' is supported by Nitronode","time":"..."} +{"level":"info","msg":"✓ Sufficient usdc balance: 50000000","time":"..."} +{"level":"info","msg":"Successfully connected to Nitronode","time":"..."} +{"level":"info","msg":"Faucet server is ready to serve requests","time":"..."} +``` + +## Security Features + +- **Address Validation**: Validates Ethereum address format before processing +- **Private Key Security**: Private key is only used for signing, never exposed +- **CORS Support**: Configurable CORS headers for web integration +- **Request Signing**: All Nitronode requests are cryptographically signed by the SDK +- **Balance Guard**: Refuses to operate below minimum balance threshold +- **URL Redaction**: `NITRONODE_URL` is never logged in full — only scheme and host are shown + +## Building for Production + +```bash +go build -o faucet-server main.go +./faucet-server +``` + +## Development + +```bash +# Run tests (from repo root) +go test ./faucet-app/... + +# Run with hot reload +go install github.com/cosmtrek/air@latest +air +``` + +## Error Handling + +- **Connection Errors**: Returns 503 if Nitronode is unavailable or reconnection fails +- **Validation Errors**: Returns 400 for invalid addresses or request format +- **Transfer Errors**: Returns 500 for Nitronode transfer failures +- **Service Unavailable**: Returns 503 if token is unsupported or balance is insufficient + +## Troubleshooting + +**Connection Issues:** + +- Verify `NITRONODE_URL` is correct and accessible +- Check firewall settings for WebSocket connections + +**Token Not Supported:** + +- Verify `TOKEN_SYMBOL` is supported by the Nitronode instance + +**Insufficient Balance:** + +- Top up the faucet wallet; the server requires at least `MIN_TRANSFER_COUNT × STANDARD_TIP_AMOUNT` available balance diff --git a/faucet-app/server/internal/config/config.go b/faucet-app/server/internal/config/config.go new file mode 100644 index 000000000..0f6c99f22 --- /dev/null +++ b/faucet-app/server/internal/config/config.go @@ -0,0 +1,108 @@ +package config + +import ( + "errors" + "fmt" + "net" + "os" + "strings" + "time" + + "github.com/ilyakaznacheev/cleanenv" + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/log" +) + +// Config holds all runtime configuration for the faucet server. +type Config struct { + ServerPort string `env:"SERVER_PORT" env-default:"8080" env-description:"HTTP server port"` + + OwnerPrivateKey string `env:"OWNER_PRIVATE_KEY" env-required:"true" env-description:"Private key for faucet owner wallet (without 0x prefix)"` + NitronodeURL string `env:"NITRONODE_URL" env-required:"true" env-description:"Nitronode WebSocket URL"` + TokenSymbol string `env:"TOKEN_SYMBOL" env-required:"true" env-description:"Token symbol to distribute (e.g., usdc, weth)"` + StandardTipAmount string `env:"STANDARD_TIP_AMOUNT" env-required:"true" env-description:"Default amount to send per request"` + MinTransferCount int `env:"MIN_TRANSFER_COUNT" env-required:"true" env-description:"Number of transfers a server should have a balance for to operate"` + CooldownPeriod string `env:"COOLDOWN_PERIOD" env-default:"24h" env-description:"Cooldown between requests per wallet/IP (e.g. 24h, 1h30m)"` + // IPRateLimitEnabled toggles the per-IP bucket. With ingress-level + // limits (limit-rps, limit-connections) handling flood protection, the + // per-IP app cooldown becomes redundant — and worse, collapses to a + // single bucket whenever TRUSTED_PROXIES is unset, since gin's + // ClientIP() then returns the ingress pod's address for every request. + IPRateLimitEnabled bool `env:"IP_RATE_LIMIT_ENABLED" env-default:"true" env-description:"Whether the per-IP rate-limit bucket is active. Disable when the ingress is enforcing per-IP limits."` + TrustedProxies string `env:"TRUSTED_PROXIES" env-default:"" env-description:"Comma-separated trusted proxy IPs; empty means direct exposure only"` + + // Log carries LOG_LEVEL / LOG_FORMAT / LOG_OUTPUT (see pkg/log). + Log log.Config + + // Parsed values (set after loading) + StandardTipAmountDecimal decimal.Decimal + CooldownPeriodDuration time.Duration + TrustedProxyList []string +} + +// Load reads configuration from a .env file (if present) or environment variables. +// A missing .env file is not an error; any other read or validation failure is. +func Load() (*Config, error) { + var config Config + + if _, statErr := os.Stat(".env"); statErr == nil { + if err := cleanenv.ReadConfig(".env", &config); err != nil { + return nil, fmt.Errorf("failed to load .env file: %w", err) + } + } else if !errors.Is(statErr, os.ErrNotExist) { + return nil, fmt.Errorf("failed to stat .env file: %w", statErr) + } else { + if err := cleanenv.ReadEnv(&config); err != nil { + return nil, fmt.Errorf("failed to load configuration from environment: %w", err) + } + } + + if err := config.Validate(); err != nil { + return nil, fmt.Errorf("config validation failed: %w", err) + } + + return &config, nil +} + +// Validate parses and range-checks all fields that require post-load processing. +func (c *Config) Validate() error { + amount, err := decimal.NewFromString(c.StandardTipAmount) + if err != nil { + return fmt.Errorf("STANDARD_TIP_AMOUNT must be a valid decimal number: %w", err) + } + if amount.IsZero() || amount.IsNegative() { + return fmt.Errorf("STANDARD_TIP_AMOUNT must be a positive number") + } + c.StandardTipAmountDecimal = amount + + d, err := time.ParseDuration(c.CooldownPeriod) + if err != nil { + return fmt.Errorf("COOLDOWN_PERIOD must be a valid duration (e.g. 24h, 1h30m): %w", err) + } + if d <= 0 { + return fmt.Errorf("COOLDOWN_PERIOD must be positive") + } + c.CooldownPeriodDuration = d + + if c.MinTransferCount <= 0 { + return fmt.Errorf("MIN_TRANSFER_COUNT must be a positive integer") + } + + if c.TrustedProxies != "" { + for _, p := range strings.Split(c.TrustedProxies, ",") { + trimmed := strings.TrimSpace(p) + if trimmed == "" { + continue + } + if net.ParseIP(trimmed) == nil { + if _, _, err := net.ParseCIDR(trimmed); err != nil { + return fmt.Errorf("TRUSTED_PROXIES contains invalid IP or CIDR %q: %w", trimmed, err) + } + } + c.TrustedProxyList = append(c.TrustedProxyList, trimmed) + } + } + + return nil +} diff --git a/faucet-app/server/internal/nitronode/client.go b/faucet-app/server/internal/nitronode/client.go new file mode 100644 index 000000000..23b5b6ed1 --- /dev/null +++ b/faucet-app/server/internal/nitronode/client.go @@ -0,0 +1,328 @@ +package nitronode + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" + "github.com/shopspring/decimal" +) + +const ( + sdkCallTimeout = 30 * time.Second + reconnectInitDelay = 300 * time.Millisecond + reconnectMaxDelay = 2 * time.Second + reconnectAttempts = 3 + pingTimeout = 3 * time.Second +) + +// TransferResult holds the result of a token transfer. +type TransferResult struct { + TxID string + Amount string + Asset string +} + +// Client wraps the Nitrolite SDK client for faucet operations. +type Client struct { + mu sync.RWMutex + sdkClient *sdk.Client + newSDKClient func() (*sdk.Client, error) // captures parsed signers; no raw key hex stored + + reconnectMu sync.Mutex // serialises reconnect attempts; not held during I/O + tokenMu sync.Mutex // serialises GetAssets; prevents N goroutines racing to validate + + logger log.Logger + ownerAddress string + tokenSymbol string + tipAmount decimal.Decimal + minTransferCount int + tokenSupported bool // cached per connection; reset in reconnect +} + +// NewClient creates a Client that wraps the Nitrolite SDK for faucet operations. +// privateKeyHex drives both message signing and tx signing. nitronodeURL is the +// WebSocket endpoint. The client is immediately connected and ready to use. +func NewClient(logger log.Logger, privateKeyHex, nitronodeURL, tokenSymbol string, tipAmount decimal.Decimal, minTransferCount int) (*Client, error) { + // Parse signers once — raw key hex is used here and not retained on the struct. + msgSigner, err := sign.NewEthereumMsgSigner(privateKeyHex) + if err != nil { + return nil, fmt.Errorf("failed to create message signer: %w", err) + } + + stateSigner, err := core.NewChannelDefaultSigner(msgSigner) + if err != nil { + return nil, fmt.Errorf("failed to create state signer: %w", err) + } + + txSigner, err := sign.NewEthereumRawSigner(privateKeyHex) + if err != nil { + return nil, fmt.Errorf("failed to create tx signer: %w", err) + } + + // factory captures already-parsed signers so reconnects don't need the raw key. + factory := func() (*sdk.Client, error) { + cl, err := sdk.NewClient(nitronodeURL, stateSigner, txSigner, + sdk.WithApplicationID("faucet"), + sdk.WithErrorHandler(func(err error) { + logger.Error("nitronode connection error", "error", err) + }), + ) + if err != nil { + return nil, fmt.Errorf("failed to connect to Nitronode: %w", err) + } + return cl, nil + } + + sdkClient, err := factory() + if err != nil { + return nil, err + } + + return &Client{ + sdkClient: sdkClient, + newSDKClient: factory, + logger: logger, + ownerAddress: sdkClient.GetUserAddress(), // immutable: all factory clients share the same signer + tokenSymbol: tokenSymbol, + tipAmount: tipAmount, + minTransferCount: minTransferCount, + }, nil +} + +// GetOwnerAddress returns the faucet owner's Ethereum address. +// Derived from the immutable raw signer; cached at construction so no lock is needed. +func (c *Client) GetOwnerAddress() string { + return c.ownerAddress +} + +// EnsureConnected checks the connection and reconnects with exponential backoff if necessary. +func (c *Client) EnsureConnected() error { + // Fast path: check WaitCh under read lock. + c.mu.RLock() + waitCh := c.sdkClient.WaitCh() + c.mu.RUnlock() + + select { + case <-waitCh: + // Connection lost; fall through. + default: + return nil + } + + // Serialise reconnect attempts; only one goroutine does the work at a time. + c.reconnectMu.Lock() + defer c.reconnectMu.Unlock() + + // Double-check under read lock — another goroutine may have reconnected while + // we waited for reconnectMu. + c.mu.RLock() + waitCh = c.sdkClient.WaitCh() + c.mu.RUnlock() + + select { + case <-waitCh: + // Still disconnected; proceed. + default: + return nil + } + + return c.reconnect() +} + +// reconnect retries SDK connection with exponential backoff. +// reconnectMu must be held by the caller; c.mu is NOT held here so I/O +// (dial + ping) does not stall readers or writers. +func (c *Client) reconnect() error { + delay := reconnectInitDelay + var lastErr error + + for attempt := 1; attempt <= reconnectAttempts; attempt++ { + c.logger.Info("reconnecting to nitronode", "attempt", attempt, "max", reconnectAttempts) + + newClient, err := c.newSDKClient() + if err != nil { + lastErr = err + c.logger.Warn("reconnect attempt failed", "attempt", attempt, "max", reconnectAttempts, "error", err) + } else { + // Ping without holding any lock. + ctx, cancel := context.WithTimeout(context.Background(), pingTimeout) + pingErr := newClient.Ping(ctx) + cancel() + + if pingErr == nil { + // Swap under write lock — fast, no I/O. + c.mu.Lock() + old := c.sdkClient + c.sdkClient = newClient + c.tokenSupported = false // re-validate on new connection + c.mu.Unlock() + + // Close old client outside the lock. + if err := old.Close(); err != nil { + c.logger.Error("error closing stale nitronode client", "error", err) + } + c.logger.Info("reconnected to nitronode", "attempt", attempt) + return nil + } + + lastErr = fmt.Errorf("ping failed: %w", pingErr) + c.logger.Warn("reconnect ping failed", "attempt", attempt, "max", reconnectAttempts, "error", pingErr) + if err := newClient.Close(); err != nil { + c.logger.Warn("error closing failed reconnect client", "error", err) + } + } + + if attempt < reconnectAttempts { + time.Sleep(delay) + delay = min(delay*2, reconnectMaxDelay) + } + } + + return fmt.Errorf("failed to reconnect after %d attempts: %w", reconnectAttempts, lastErr) +} + +// EnsureOperational validates token support and sufficient balance. +func (c *Client) EnsureOperational() error { + if err := c.validateTokenSupport(c.tokenSymbol); err != nil { + return fmt.Errorf("token validation failed: %w", err) + } + + if err := c.validateFaucetBalance(c.tokenSymbol, c.tipAmount, c.minTransferCount); err != nil { + return fmt.Errorf("balance check failed: %w", err) + } + + return nil +} + +func (c *Client) validateTokenSupport(tokenSymbol string) error { + // Fast path: already confirmed for this connection. + c.mu.RLock() + if c.tokenSupported { + c.mu.RUnlock() + return nil + } + c.mu.RUnlock() + + // Serialise the GetAssets call so only one goroutine fetches at a time. + c.tokenMu.Lock() + defer c.tokenMu.Unlock() + + // Double-check after acquiring tokenMu. + c.mu.RLock() + cached := c.tokenSupported + cl := c.sdkClient + c.mu.RUnlock() + + if cached { + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), sdkCallTimeout) + defer cancel() + + assets, err := cl.GetAssets(ctx, nil) + if err != nil { + return fmt.Errorf("failed to fetch supported assets: %w", err) + } + + for _, asset := range assets { + if strings.EqualFold(asset.Symbol, tokenSymbol) { + c.logger.Debug("token supported by nitronode", "token", tokenSymbol) + c.mu.Lock() + if c.sdkClient == cl { // guard against reconnect between fetch and write + c.tokenSupported = true + } + c.mu.Unlock() + return nil + } + } + + return fmt.Errorf("token '%s' is not supported by Nitronode", tokenSymbol) +} + +func (c *Client) validateFaucetBalance(tokenSymbol string, tipAmount decimal.Decimal, minTransferCount int) error { + ctx, cancel := context.WithTimeout(context.Background(), sdkCallTimeout) + defer cancel() + + c.mu.RLock() + cl := c.sdkClient + c.mu.RUnlock() + + ownerAddress := cl.GetUserAddress() + + balances, err := cl.GetBalances(ctx, ownerAddress) + if err != nil { + return fmt.Errorf("failed to fetch faucet balance: %w", err) + } + + minRequired := tipAmount.Mul(decimal.NewFromInt(int64(minTransferCount))) + + for _, balance := range balances { + if strings.EqualFold(balance.Asset, tokenSymbol) { + if balance.Balance.LessThan(minRequired) { + return fmt.Errorf("insufficient %s balance: %s (required: %s for %d transfers)", + tokenSymbol, balance.Balance.String(), minRequired.String(), minTransferCount) + } + c.logger.Info("sufficient balance", + "token", tokenSymbol, + "balance", balance.Balance.String(), + ) + if balance.Enforced.IsPositive() && balance.Enforced.LessThan(balance.Balance) { + c.logger.Warn("enforced balance below channel balance; consider checkpointing", + "token", tokenSymbol, + "enforced", balance.Enforced.String(), + "channel", balance.Balance.String(), + ) + } + return nil + } + } + + return fmt.Errorf("insufficient %s balance: 0 (required: %s for %d transfers)", + tokenSymbol, minRequired.String(), minTransferCount) +} + +// Transfer sends tokens to the destination address. +func (c *Client) Transfer(destination, asset string, amount decimal.Decimal) (*TransferResult, error) { + ctx, cancel := context.WithTimeout(context.Background(), sdkCallTimeout) + defer cancel() + + c.mu.RLock() + cl := c.sdkClient + c.mu.RUnlock() + + state, err := cl.Transfer(ctx, destination, asset, amount) + if err != nil { + return nil, fmt.Errorf("transfer failed: %w", err) + } + + result := &TransferResult{ + TxID: state.Transition.TxID, + Amount: state.Transition.Amount.String(), + Asset: state.Asset, + } + + if result.Amount == "" { + result.Amount = amount.String() + } + if result.Asset == "" { + result.Asset = asset + } + + return result, nil +} + +// Close shuts down the Nitronode connection. +// Uses write lock to serialise with reconnect and prevent closing a freshly installed client. +func (c *Client) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.sdkClient.Close() +} diff --git a/faucet-app/server/internal/nitronode/client_validation_test.go b/faucet-app/server/internal/nitronode/client_validation_test.go new file mode 100644 index 000000000..8b34cf120 --- /dev/null +++ b/faucet-app/server/internal/nitronode/client_validation_test.go @@ -0,0 +1,41 @@ +package nitronode + +import ( + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/log" +) + +func TestNewClientValidation(t *testing.T) { + t.Run("should fail with invalid private key", func(t *testing.T) { + client, err := NewClient(log.NewNoopLogger(), "not-a-valid-key", "ws://localhost:8080", "usdc", decimal.NewFromInt(10), 1) + + assert.Nil(t, client) + require.Error(t, err) + }) + + t.Run("should fail with invalid URL", func(t *testing.T) { + validKey := "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + + client, err := NewClient(log.NewNoopLogger(), validKey, "ws://localhost:19999", "usdc", decimal.NewFromInt(10), 1) + + assert.Nil(t, client) + require.Error(t, err) + }) + + t.Run("should handle 0x prefixed key", func(t *testing.T) { + // NewEthereumMsgSigner should handle 0x prefix; if it fails, err is non-nil. + // Either outcome is acceptable — the test just asserts no panic and consistency. + key := "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + + client, err := NewClient(log.NewNoopLogger(), key, "ws://localhost:19999", "usdc", decimal.NewFromInt(10), 1) + if err == nil { + require.NotNil(t, client) + require.NoError(t, client.Close()) + } + }) +} diff --git a/faucet-app/server/internal/server/ratelimiter.go b/faucet-app/server/internal/server/ratelimiter.go new file mode 100644 index 000000000..854544635 --- /dev/null +++ b/faucet-app/server/internal/server/ratelimiter.go @@ -0,0 +1,74 @@ +package server + +import ( + "sync" + "time" +) + +type rateLimiter struct { + mu sync.Mutex + cooldown time.Duration + seen map[string]time.Time + calls uint64 +} + +func newRateLimiter(cooldown time.Duration) *rateLimiter { + return &rateLimiter{ + cooldown: cooldown, + seen: make(map[string]time.Time), + } +} + +// checkAndRecord atomically checks if key is allowed and, if so, records the attempt. +// Returns true if allowed (cooldown slot consumed), false if on cooldown. +func (r *rateLimiter) checkAndRecord(key string) bool { + now := time.Now() + r.mu.Lock() + defer r.mu.Unlock() + r.calls++ + r.evictExpiredLocked(now) + last, exists := r.seen[key] + if exists && now.Sub(last) < r.cooldown { + return false + } + r.seen[key] = now + return true +} + +// checkAndRecordBoth atomically checks both addr and ip. Only records both if +// both pass — prevents a blocked IP from burning the wallet's cooldown slot. +// Returns (false, "address") or (false, "ip") if either is blocked. +func (r *rateLimiter) checkAndRecordBoth(addr, ip string) (bool, string) { + now := time.Now() + r.mu.Lock() + defer r.mu.Unlock() + + r.calls++ + r.evictExpiredLocked(now) + + if last, ok := r.seen[addr]; ok && now.Sub(last) < r.cooldown { + return false, "address" + } + if last, ok := r.seen[ip]; ok && now.Sub(last) < r.cooldown { + return false, "ip" + } + + r.seen[addr] = now + if ip != addr { + r.seen[ip] = now + } + return true, "" +} + +// evictExpiredLocked removes entries that are past the cooldown window. +// Must be called with r.mu held. Runs every 1024 calls to amortise cost. +func (r *rateLimiter) evictExpiredLocked(now time.Time) { + if r.calls%1024 != 0 { + return + } + for k, ts := range r.seen { + if now.Sub(ts) >= r.cooldown { + delete(r.seen, k) + } + } +} diff --git a/faucet-app/server/internal/server/server.go b/faucet-app/server/internal/server/server.go new file mode 100644 index 000000000..10fe13942 --- /dev/null +++ b/faucet-app/server/internal/server/server.go @@ -0,0 +1,258 @@ +package server + +import ( + "net/http" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/gin-gonic/gin" + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/faucet-app/server/internal/config" + "github.com/layer-3/nitrolite/faucet-app/server/internal/nitronode" + "github.com/layer-3/nitrolite/pkg/log" +) + +// NitronodeClient is the interface the server uses to interact with Nitronode. +type NitronodeClient interface { + GetOwnerAddress() string + EnsureConnected() error + EnsureOperational() error + Transfer(destination, asset string, amount decimal.Decimal) (*nitronode.TransferResult, error) +} + +// Error message constants +const ( + ErrInvalidRequestFormat = "Invalid request format. Expected JSON with 'userAddress' field." + ErrInvalidAddressFormat = "Invalid address format." + ErrNitronodeConnectionFailed = "Failed to connect to Nitronode." + ErrServiceUnavailable = "Faucet service is currently unavailable." + ErrTransferFailed = "Failed to send tokens." + ErrRateLimitExceeded = "Rate limit exceeded. Please try again later." + MsgTokensSentSuccessfully = "Tokens sent successfully" +) + +type Server struct { + logger log.Logger + config *config.Config + nitronodeClient NitronodeClient + router *gin.Engine + rateLimiter *rateLimiter +} + +type FaucetRequest struct { + UserAddress string `json:"userAddress" binding:"required"` +} + +type FaucetResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + TxID string `json:"txId,omitempty"` + Amount string `json:"amount,omitempty"` + Asset string `json:"asset,omitempty"` + Destination string `json:"destination,omitempty"` +} + +type ErrorResponse struct { + Error string `json:"error"` +} + +func NewServer(logger log.Logger, cfg *config.Config, client NitronodeClient) *Server { + if cfg.Log.Level == log.LevelDebug { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + + router := gin.New() + if len(cfg.TrustedProxyList) > 0 { + router.SetTrustedProxies(cfg.TrustedProxyList) + } else { + // No proxies configured: c.ClientIP() uses RemoteAddr directly. + // Set TRUSTED_PROXIES if the faucet is behind an ingress or load balancer, + // otherwise IP-based rate limiting will collapse to one bucket per proxy. + router.SetTrustedProxies(nil) + } + + // Add middleware + router.Use(gin.Recovery()) + router.Use(requestLogger(logger)) + router.Use(corsMiddleware()) + + server := &Server{ + logger: logger, + config: cfg, + nitronodeClient: client, + router: router, + rateLimiter: newRateLimiter(cfg.CooldownPeriodDuration), + } + + server.setupRoutes() + return server +} + +func (s *Server) setupRoutes() { + s.router.POST("/requestTokens", s.requestTokens) + s.router.GET("/info", s.getInfo) +} + +func (s *Server) getInfo(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "service": "Nitrolite Faucet Server", + "version": "1.0.0", + "faucet_address": s.nitronodeClient.GetOwnerAddress(), + "standard_tip_amount": s.config.StandardTipAmountDecimal.String(), + "token_symbol": s.config.TokenSymbol, + "endpoints": []string{"/requestTokens"}, + }) +} + +func (s *Server) requestTokens(c *gin.Context) { + var req FaucetRequest + if err := c.ShouldBindJSON(&req); err != nil { + s.logger.Warn("invalid request format", "error", err) + c.JSON(http.StatusBadRequest, ErrorResponse{ + Error: ErrInvalidRequestFormat, + }) + return + } + + // Validate the user address + userAddress := strings.TrimSpace(req.UserAddress) + if !common.IsHexAddress(userAddress) { + s.logger.Warn("invalid address format", "address", userAddress) + c.JSON(http.StatusBadRequest, ErrorResponse{ + Error: ErrInvalidAddressFormat, + }) + return + } + + userAddress = common.HexToAddress(userAddress).Hex() + + // Atomically check-and-record. When the per-IP bucket is enabled, both + // keys are checked under one lock so a blocked IP cannot burn the wallet's + // cooldown slot. When disabled (ingress handles per-IP flood protection), + // only the wallet bucket is checked. Every accepted request — including + // ones that later fail — consumes the slot, blocking probing via induced + // failures. + clientIP := c.ClientIP() + var ( + allowed bool + blocked string + ) + if s.config.IPRateLimitEnabled { + allowed, blocked = s.rateLimiter.checkAndRecordBoth(userAddress, clientIP) + } else { + allowed = s.rateLimiter.checkAndRecord(userAddress) + blocked = "address" + } + if !allowed { + if blocked == "address" { + s.logger.Warn("rate limit exceeded", "key", "address", "address", userAddress) + } else { + s.logger.Warn("rate limit exceeded", "key", "ip", "ip", clientIP, "address", userAddress) + } + c.JSON(http.StatusTooManyRequests, ErrorResponse{Error: ErrRateLimitExceeded}) + return + } + + s.logger.Info("processing faucet request", "address", userAddress) + + // Ensure client is connected + if err := s.nitronodeClient.EnsureConnected(); err != nil { + s.logger.Error("connection failed", "address", userAddress, "error", err) + c.JSON(http.StatusServiceUnavailable, ErrorResponse{ + Error: ErrNitronodeConnectionFailed, + }) + return + } + + // Ensure client is operational + if err := s.nitronodeClient.EnsureOperational(); err != nil { + s.logger.Error("service not operational", "address", userAddress, "error", err) + c.JSON(http.StatusServiceUnavailable, ErrorResponse{ + Error: ErrServiceUnavailable, + }) + return + } + + // Perform the transfer + result, err := s.nitronodeClient.Transfer( + userAddress, + s.config.TokenSymbol, + s.config.StandardTipAmountDecimal, + ) + if err != nil { + s.logger.Error("transfer failed", "address", userAddress, "error", err) + c.JSON(http.StatusInternalServerError, ErrorResponse{ + Error: ErrTransferFailed, + }) + return + } + if result == nil { + s.logger.Error("transfer returned nil result", "address", userAddress) + c.JSON(http.StatusInternalServerError, ErrorResponse{ + Error: ErrTransferFailed, + }) + return + } + + txID := result.TxID + amount := result.Amount + asset := result.Asset + + s.logger.Info("transfer successful", + "address", userAddress, + "amount", amount, + "asset", asset, + "txID", txID, + ) + + c.JSON(http.StatusOK, FaucetResponse{ + Success: true, + Message: MsgTokensSentSuccessfully, + TxID: txID, + Amount: amount, + Asset: asset, + Destination: userAddress, + }) +} + +func (s *Server) Start() error { + addr := ":" + s.config.ServerPort + s.logger.Info("starting HTTP server", "port", s.config.ServerPort) + return s.router.Run(addr) +} + +// Middleware functions + +func requestLogger(logger log.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("request", + "method", c.Request.Method, + "path", c.Request.URL.Path, + "client_ip", c.ClientIP(), + ) + c.Next() + logger.Debug("response", + "method", c.Request.Method, + "path", c.Request.URL.Path, + "status", c.Writer.Status(), + ) + } +} + +func corsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + return + } + + c.Next() + } +} diff --git a/faucet-app/server/internal/server/server_test.go b/faucet-app/server/internal/server/server_test.go new file mode 100644 index 000000000..cb8110631 --- /dev/null +++ b/faucet-app/server/internal/server/server_test.go @@ -0,0 +1,347 @@ +package server + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/faucet-app/server/internal/config" + "github.com/layer-3/nitrolite/faucet-app/server/internal/nitronode" + "github.com/layer-3/nitrolite/pkg/log" +) + +// mockNitronodeClient is a simple in-memory mock implementing NitronodeClient. +type mockNitronodeClient struct { + ownerAddress string + connErr error + operationalErr error + transferResult *nitronode.TransferResult + transferErr error + capturedDest string + capturedAsset string + capturedAmount decimal.Decimal +} + +func (m *mockNitronodeClient) GetOwnerAddress() string { return m.ownerAddress } +func (m *mockNitronodeClient) EnsureConnected() error { return m.connErr } +func (m *mockNitronodeClient) EnsureOperational() error { return m.operationalErr } +func (m *mockNitronodeClient) Transfer(dest, asset string, amount decimal.Decimal) (*nitronode.TransferResult, error) { + m.capturedDest = dest + m.capturedAsset = asset + m.capturedAmount = amount + return m.transferResult, m.transferErr +} + +func defaultConfig() *config.Config { + return &config.Config{ + ServerPort: "0", + OwnerPrivateKey: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + NitronodeURL: "ws://localhost:0", + TokenSymbol: "usdc", + StandardTipAmount: "10", + StandardTipAmountDecimal: decimal.RequireFromString("10"), + CooldownPeriod: "24h", + CooldownPeriodDuration: 24 * time.Hour, + IPRateLimitEnabled: true, + Log: log.Config{Level: log.LevelDebug}, + } +} + +func defaultMock() *mockNitronodeClient { + return &mockNitronodeClient{ + ownerAddress: "0x9fc51BEE23Fb53569c46CcF013400f0E19524bd2", + transferResult: &nitronode.TransferResult{ + TxID: "tx-abc123", + Amount: "10", + Asset: "usdc", + }, + } +} + +func TestRequestTokens_Success(t *testing.T) { + mock := defaultMock() + srv := NewServer(log.NewNoopLogger(), defaultConfig(), mock) + + testAddress := common.HexToAddress("0x742D35CC6634c0532925a3B8c17D18fBe3b78890").Hex() + body, err := json.Marshal(FaucetRequest{UserAddress: testAddress}) + require.NoError(t, err) + + req := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var resp FaucetResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.True(t, resp.Success) + assert.Equal(t, MsgTokensSentSuccessfully, resp.Message) + assert.Equal(t, "tx-abc123", resp.TxID) + assert.Equal(t, "10", resp.Amount) + assert.Equal(t, "usdc", resp.Asset) + assert.Equal(t, testAddress, resp.Destination) + + assert.Equal(t, testAddress, mock.capturedDest) + assert.Equal(t, "usdc", mock.capturedAsset) + assert.True(t, decimal.RequireFromString("10").Equal(mock.capturedAmount)) +} + +func TestRequestTokens_InvalidAddress(t *testing.T) { + srv := NewServer(log.NewNoopLogger(), defaultConfig(), defaultMock()) + + body, err := json.Marshal(FaucetRequest{UserAddress: "not-an-address"}) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + var resp ErrorResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, ErrInvalidAddressFormat, resp.Error) +} + +func TestRequestTokens_MissingField(t *testing.T) { + srv := NewServer(log.NewNoopLogger(), defaultConfig(), defaultMock()) + + body, err := json.Marshal(map[string]string{"wrongField": "0x1234"}) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + var resp ErrorResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, ErrInvalidRequestFormat, resp.Error) +} + +func TestRequestTokens_ConnectionFailure(t *testing.T) { + mock := defaultMock() + mock.connErr = assert.AnError + srv := NewServer(log.NewNoopLogger(), defaultConfig(), mock) + + body, err := json.Marshal(FaucetRequest{UserAddress: "0x742d35Cc6634C0532925a3b8c17d18fBE3b78890"}) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusServiceUnavailable, w.Code) + var resp ErrorResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, ErrNitronodeConnectionFailed, resp.Error) +} + +func TestRequestTokens_OperationalFailure(t *testing.T) { + mock := defaultMock() + mock.operationalErr = assert.AnError + srv := NewServer(log.NewNoopLogger(), defaultConfig(), mock) + + body, err := json.Marshal(FaucetRequest{UserAddress: "0x742d35Cc6634C0532925a3b8c17d18fBE3b78890"}) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusServiceUnavailable, w.Code) + var resp ErrorResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, ErrServiceUnavailable, resp.Error) +} + +func TestRequestTokens_TransferFailure(t *testing.T) { + mock := defaultMock() + mock.transferResult = nil + mock.transferErr = assert.AnError + srv := NewServer(log.NewNoopLogger(), defaultConfig(), mock) + + body, err := json.Marshal(FaucetRequest{UserAddress: "0x742d35Cc6634C0532925a3b8c17d18fBE3b78890"}) + require.NoError(t, err) + req := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + var resp ErrorResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, ErrTransferFailed, resp.Error) +} + +func TestRateLimiting(t *testing.T) { + cfg := defaultConfig() + cfg.CooldownPeriodDuration = 24 * time.Hour + + t.Run("second request from same wallet is rejected", func(t *testing.T) { + mock := defaultMock() + srv := NewServer(log.NewNoopLogger(), cfg, mock) + + testAddress := common.HexToAddress("0x742D35CC6634c0532925a3B8c17D18fBe3b78890").Hex() + body, err := json.Marshal(FaucetRequest{UserAddress: testAddress}) + require.NoError(t, err) + + // First request — should succeed + req1 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req1.Header.Set("Content-Type", "application/json") + w1 := httptest.NewRecorder() + srv.router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusOK, w1.Code) + + // Second request same wallet — should be rate limited + req2 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req2.Header.Set("Content-Type", "application/json") + w2 := httptest.NewRecorder() + srv.router.ServeHTTP(w2, req2) + assert.Equal(t, http.StatusTooManyRequests, w2.Code) + + var resp ErrorResponse + require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &resp)) + assert.Equal(t, ErrRateLimitExceeded, resp.Error) + }) + + t.Run("failed transfer consumes rate limit slot", func(t *testing.T) { + mock := defaultMock() + mock.transferResult = nil + mock.transferErr = assert.AnError + srv := NewServer(log.NewNoopLogger(), cfg, mock) + + testAddress := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").Hex() + body, err := json.Marshal(FaucetRequest{UserAddress: testAddress}) + require.NoError(t, err) + + // First request fails at transfer but still consumes the rate-limit slot. + req1 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req1.Header.Set("Content-Type", "application/json") + w1 := httptest.NewRecorder() + srv.router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusInternalServerError, w1.Code) + + // Second request is rate-limited because the slot was consumed on the first attempt. + req2 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body)) + req2.Header.Set("Content-Type", "application/json") + w2 := httptest.NewRecorder() + srv.router.ServeHTTP(w2, req2) + assert.Equal(t, http.StatusTooManyRequests, w2.Code) + }) + + t.Run("different wallets from different IPs are not rate limited by each other", func(t *testing.T) { + mock := defaultMock() + srv := NewServer(log.NewNoopLogger(), cfg, mock) + + addr1 := common.HexToAddress("0x742D35CC6634c0532925a3B8c17D18fBe3b78890").Hex() + addr2 := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").Hex() + + body1, _ := json.Marshal(FaucetRequest{UserAddress: addr1}) + body2, _ := json.Marshal(FaucetRequest{UserAddress: addr2}) + + req1 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body1)) + req1.Header.Set("Content-Type", "application/json") + req1.RemoteAddr = "10.0.0.1:1234" + w1 := httptest.NewRecorder() + srv.router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusOK, w1.Code) + + req2 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body2)) + req2.Header.Set("Content-Type", "application/json") + req2.RemoteAddr = "10.0.0.2:1234" + w2 := httptest.NewRecorder() + srv.router.ServeHTTP(w2, req2) + assert.Equal(t, http.StatusOK, w2.Code) + }) + + t.Run("same IP with different wallet is still rate limited", func(t *testing.T) { + mock := defaultMock() + srv := NewServer(log.NewNoopLogger(), cfg, mock) + + addr1 := common.HexToAddress("0x742D35CC6634c0532925a3B8c17D18fBe3b78890").Hex() + addr2 := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").Hex() + + body1, _ := json.Marshal(FaucetRequest{UserAddress: addr1}) + body2, _ := json.Marshal(FaucetRequest{UserAddress: addr2}) + + req1 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body1)) + req1.Header.Set("Content-Type", "application/json") + w1 := httptest.NewRecorder() + srv.router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusOK, w1.Code) + + // Different wallet, same IP — should be blocked by IP limit + req2 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body2)) + req2.Header.Set("Content-Type", "application/json") + w2 := httptest.NewRecorder() + srv.router.ServeHTTP(w2, req2) + assert.Equal(t, http.StatusTooManyRequests, w2.Code) + }) + + t.Run("with IP rate limit disabled, same IP different wallet succeeds", func(t *testing.T) { + mock := defaultMock() + cfgNoIP := *cfg + cfgNoIP.IPRateLimitEnabled = false + srv := NewServer(log.NewNoopLogger(), &cfgNoIP, mock) + + addr1 := common.HexToAddress("0x742D35CC6634c0532925a3B8c17D18fBe3b78890").Hex() + addr2 := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF").Hex() + + body1, _ := json.Marshal(FaucetRequest{UserAddress: addr1}) + body2, _ := json.Marshal(FaucetRequest{UserAddress: addr2}) + + req1 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body1)) + req1.Header.Set("Content-Type", "application/json") + w1 := httptest.NewRecorder() + srv.router.ServeHTTP(w1, req1) + assert.Equal(t, http.StatusOK, w1.Code) + + // Different wallet, same IP — accepted because per-IP bucket is off. + req2 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body2)) + req2.Header.Set("Content-Type", "application/json") + w2 := httptest.NewRecorder() + srv.router.ServeHTTP(w2, req2) + assert.Equal(t, http.StatusOK, w2.Code) + + // Same wallet still rejected — per-address cooldown stays in force. + req3 := httptest.NewRequest("POST", "/requestTokens", bytes.NewReader(body1)) + req3.Header.Set("Content-Type", "application/json") + w3 := httptest.NewRecorder() + srv.router.ServeHTTP(w3, req3) + assert.Equal(t, http.StatusTooManyRequests, w3.Code) + }) +} + +func TestInfoEndpoint(t *testing.T) { + mock := defaultMock() + srv := NewServer(log.NewNoopLogger(), defaultConfig(), mock) + + req := httptest.NewRequest("GET", "/info", nil) + w := httptest.NewRecorder() + + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + var resp map[string]interface{} + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(t, "Nitrolite Faucet Server", resp["service"]) + assert.Equal(t, "1.0.0", resp["version"]) + assert.Equal(t, mock.ownerAddress, resp["faucet_address"]) + assert.Equal(t, "10", resp["standard_tip_amount"]) + assert.Equal(t, "usdc", resp["token_symbol"]) +} diff --git a/faucet-app/server/main.go b/faucet-app/server/main.go new file mode 100644 index 000000000..7ba119bb3 --- /dev/null +++ b/faucet-app/server/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "net/url" + "os" + "os/signal" + "syscall" + + "github.com/layer-3/nitrolite/faucet-app/server/internal/config" + "github.com/layer-3/nitrolite/faucet-app/server/internal/nitronode" + "github.com/layer-3/nitrolite/faucet-app/server/internal/server" + "github.com/layer-3/nitrolite/pkg/log" +) + +func redactURL(raw string) string { + u, err := url.Parse(raw) + if err != nil || u.Host == "" { + return "[invalid URL]" + } + return fmt.Sprintf("%s://%s", u.Scheme, u.Host) +} + +func main() { + cfg, err := config.Load() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load configuration: %v\n", err) + os.Exit(1) + } + + logger := log.NewZapLogger(cfg.Log).WithName("faucet") + + logger.Info("starting nitrolite faucet server") + logger.Info("configuration loaded", + "server_port", cfg.ServerPort, + "nitronode_url", redactURL(cfg.NitronodeURL), + ) + + client, err := nitronode.NewClient(logger.WithName("nitronode"), cfg.OwnerPrivateKey, cfg.NitronodeURL, cfg.TokenSymbol, cfg.StandardTipAmountDecimal, cfg.MinTransferCount) + if err != nil { + logger.Fatal("failed to create nitronode client", "error", err) + } + + if err := client.EnsureOperational(); err != nil { + logger.Fatal("operational check failed", "error", err) + } + + logger.Info("connected to nitronode", "owner_address", client.GetOwnerAddress()) + + httpServer := server.NewServer(logger.WithName("http"), cfg, client) + + go func() { + if err := httpServer.Start(); err != nil { + logger.Fatal("failed to start HTTP server", "error", err) + } + }() + + logger.Info("faucet server ready") + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + logger.Info("shutting down") + + if err := client.Close(); err != nil { + logger.Error("error closing nitronode connection", "error", err) + } + + logger.Info("shutdown complete") +} diff --git a/go.mod b/go.mod index 0158c35d1..87472649f 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,25 @@ module github.com/layer-3/nitrolite -go 1.25.0 +go 1.25.9 require ( - cloud.google.com/go/kms v1.26.0 + cloud.google.com/go/kms v1.31.0 github.com/c-bata/go-prompt v0.2.6 - github.com/ethereum/go-ethereum v1.17.1 + github.com/ethereum/go-ethereum v1.17.3 + github.com/gin-gonic/gin v1.12.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 + github.com/ilyakaznacheev/cleanenv v1.5.0 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/shopspring/decimal v1.4.0 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 - go.yaml.in/yaml/v2 v2.4.2 - golang.org/x/term v0.40.0 - google.golang.org/api v0.269.0 + github.com/testcontainers/testcontainers-go v0.42.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 + go.yaml.in/yaml/v2 v2.4.4 + golang.org/x/term v0.43.0 + google.golang.org/api v0.281.0 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 @@ -24,29 +27,53 @@ require ( require ( cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.18.2 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/longrunning v0.8.0 // indirect + cloud.google.com/go/iam v1.7.0 // indirect + cloud.google.com/go/longrunning v0.9.0 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect + github.com/googleapis/gax-go/v2 v2.22.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-tty v0.0.3 // indirect + github.com/moby/moby/api v1.54.2 // indirect + github.com/moby/moby/client v0.4.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // 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 - golang.org/x/net v0.50.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/time v0.14.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 + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/net v0.55.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-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/grpc v1.81.1 // indirect ) require ( @@ -64,15 +91,14 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect + github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect 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/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // 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 @@ -81,63 +107,59 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect - github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.8.0 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jmoiron/sqlx v1.4.0 - github.com/klauspost/compress v1.18.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-sqlite3 v1.14.34 + github.com/mattn/go-sqlite3 v1.14.44 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/patternmatcher v0.6.0 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/pressly/goose/v3 v3.27.0 - github.com/prometheus/client_model v0.6.2 // indirect + github.com/pressly/goose/v3 v3.27.1 + github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.19.2 // indirect + github.com/prometheus/procfs v0.20.1 // indirect 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/shopspring/decimal v1.4.0 - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/shirou/gopsutil/v4 v4.26.3 // indirect github.com/supranational/blst v0.3.16 // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // 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.41.0 - go.opentelemetry.io/otel/metric v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.41.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.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/text v0.34.0 // indirect + go.uber.org/zap v1.28.0 + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 gorm.io/datatypes v1.2.7 diff --git a/go.sum b/go.sum index 1ca2f8cda..59a04627b 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,17 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= -cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/kms v1.26.0 h1:cK9mN2cf+9V63D3H1f6koxTatWy39aTI/hCjz1I+adU= -cloud.google.com/go/kms v1.26.0/go.mod h1:pHKOdFJm63hxBsiPkYtowZPltu9dW0MWvBa6IA4HM58= -cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= -cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +cloud.google.com/go/iam v1.7.0 h1:JD3zh0C6LHl16aCn5Akff0+GELdp1+4hmh6ndoFLl8U= +cloud.google.com/go/iam v1.7.0/go.mod h1:tetWZW1PD/m6vcuY2Zj/aU0eCHNPuxedbnbRTyKXvdY= +cloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE= +cloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= +cloud.google.com/go/longrunning v0.9.0 h1:0EzbDEGsAvOZNbqXopgniY0w0a1phvu5IdUFq8grmqY= +cloud.google.com/go/longrunning v0.9.0/go.mod h1:pkTz846W7bF4o2SzdWJ40Hu0Re+UoNT6Q5t+igIcb8E= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= @@ -36,18 +36,24 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= @@ -74,10 +80,10 @@ github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GK github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= -github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc= +github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -93,39 +99,43 @@ 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/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-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= 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= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -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/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= 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.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho= -github.com/ethereum/go-ethereum v1.17.1/go.mod h1:7UWOVHL7K3b8RfVRea022btnzLCaanwHtBuH1jUCH/I= +github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= +github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= 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= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -134,10 +144,22 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -154,16 +176,17 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/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/googleapis/enterprise-certificate-proxy v0.3.16 h1:F/VPrx0YPBdksZJQdCAp0WUsqnNmZpUZszzfYt0M5Dw= +github.com/googleapis/enterprise-certificate-proxy v0.3.16/go.mod h1:9Yb0eAkH/Xqhvv3zbeKf/+wMJqCeocWc6KIhDvEAuYE= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac= @@ -172,8 +195,6 @@ github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasn github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= @@ -196,8 +217,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= -github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -210,12 +231,14 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jsternberg/zap-logfmt v1.3.0 h1:z1n1AOHVVydOOVuyphbOKyR4NICDQFiJMn1IK5hVQ5Y= github.com/jsternberg/zap-logfmt v1.3.0/go.mod h1:N3DENp9WNmCZxvkBD/eReWwz1149BK6jEN9cQ4fNwZE= -github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= -github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -224,6 +247,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= @@ -238,23 +263,23 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= -github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw= -github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4= +github.com/microsoft/go-mssqldb v1.9.8 h1:d4IFMvF/o+HdpXUqbBfzHvn/NlFA75YGcfHUUvDFJEM= +github.com/microsoft/go-mssqldb v1.9.8/go.mod h1:eGSRSGAW4hKMy5YcAenhCDjIRm2rhqIdmmwgciMzLus= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -263,12 +288,14 @@ 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/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= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +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/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= @@ -277,8 +304,11 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= @@ -289,6 +319,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= @@ -311,16 +343,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= -github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= +github.com/pressly/goose/v3 v3.27.1 h1:6uEvcprBybDmW4hcz3gYujhARhye+GoWKhEWyzD5sh4= +github.com/pressly/goose/v3 v3.27.1/go.mod h1:maruOxsPnIG2yHHyo8UqKWXYKFcH7Q76csUV7+7KYoM= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -337,78 +373,91 @@ 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.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/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= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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 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.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= +github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo= +github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs= +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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= 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= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= -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.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= -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.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -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/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.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= -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.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= 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= -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= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +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= @@ -419,31 +468,28 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/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= -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/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= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +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.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.281.0 h1:sohYgszGGg3zC0P6ncdV6J9IOBtN9LVfFKz8C9tTtJU= +google.golang.org/api v0.281.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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= @@ -471,13 +517,15 @@ gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= -modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0= +modernc.org/libc v1.72.1 h1:db1xwJ6u1kE3KHTFTTbe2GCrczHPKzlURP0aDC4NGD0= +modernc.org/libc v1.72.1/go.mod h1:HRMiC/PhPGLIPM7GzAFCbI+oSgE3dhZ8FWftmRrHVlY= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= -modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U= +modernc.org/sqlite v1.49.1/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/helmfile.yaml.gotmpl b/helmfile.yaml.gotmpl new file mode 100644 index 000000000..2e1d8f0c9 --- /dev/null +++ b/helmfile.yaml.gotmpl @@ -0,0 +1,59 @@ +environments: + sandbox-v1: + values: + - nitronode/chart/config/profiles.yaml + stress-v1: + values: + - nitronode/chart/config/profiles.yaml + prod-v1: + values: + - nitronode/chart/config/profiles.yaml + +--- + +releases: + - name: nitronode + namespace: nitronode-{{ .Environment.Name }} + chart: ./nitronode/chart + installed: true + secrets: + - nitronode/chart/config/secrets.yaml + - nitronode/chart/config/{{ .Environment.Name }}/secrets.yaml + values: + - nitronode/chart/config/{{ .Environment.Name }}/nitronode.yaml.gotmpl + set: + - name: image.tag + value: '{{ env "NITRONODE_IMAGE_TAG" | default (requiredEnv "IMAGE_TAG") }}' + - name: config.blockchains + file: nitronode/chart/config/{{ .Environment.Name }}/blockchains.yaml + - name: config.assets + file: nitronode/chart/config/{{ .Environment.Name }}/assets.yaml + + - name: faucet-app + namespace: nitronode-{{ .Environment.Name }} + chart: ./faucet-app/chart + # Faucet ships only to non-prod envs. + installed: {{ ne .Environment.Name "prod-v1" }} + needs: + - nitronode-{{ .Environment.Name }}/nitronode + {{- if ne .Environment.Name "prod-v1" }} + secrets: + - faucet-app/chart/config/{{ .Environment.Name }}/secrets.yaml + {{- end }} + values: + - faucet-app/chart/config/{{ .Environment.Name }}/faucet.yaml.gotmpl + set: + - name: image.tag + value: '{{ env "FAUCET_IMAGE_TAG" | default (requiredEnv "IMAGE_TAG") }}' + + - name: playground + namespace: nitronode-{{ .Environment.Name }} + chart: ./playground/chart + installed: true + needs: + - nitronode-{{ .Environment.Name }}/nitronode + values: + - playground/chart/config/{{ .Environment.Name }}/playground.yaml.gotmpl + set: + - name: image.tag + value: '{{ env "PLAYGROUND_IMAGE_TAG" | default (requiredEnv "IMAGE_TAG") }}' diff --git a/llms-full.txt b/llms-full.txt new file mode 100644 index 000000000..c696e6f1b --- /dev/null +++ b/llms-full.txt @@ -0,0 +1,385 @@ +# Nitrolite — Full AI Reference + +This document provides a comprehensive reference for AI agents and LLMs working with the Nitrolite state channel protocol and SDKs. It is designed to be loaded as context in Claude Projects, ChatGPT, or any AI tool. + +--- + +## 1. What is Nitrolite? + +Nitrolite is a state channel protocol for Ethereum and EVM-compatible blockchains. It enables high-speed off-chain interactions (instant finality, near-zero gas) while preserving on-chain security guarantees. + +Users exchange signed state updates off-chain with Nodes that act as a hub. Any user can enforce the latest agreed state on the blockchain at any time. + +### System Roles + +- **User** — Opens channels, signs state updates, holds assets within the protocol +- **Node (Nitronode, formerly Clearnode)** — Facilitates off-chain state advancement, manages channels, syncs with blockchain +- **Blockchain** — On-chain storage/execution layer that validates states, stores checkpoints, resolves disputes + +### Design Goals + +- Off-chain scalability — minimize on-chain transactions +- Blockchain security — any user can fall back to on-chain enforcement +- Cross-chain asset interaction — unified asset model across blockchains +- Extensibility — app sessions, custom logic via extensions + +--- + +## 2. Core Concepts + +### Channels + +A channel is a state container shared between a User and a Node. It holds asset allocations and supports off-chain state updates. Each channel is defined by immutable parameters: user, node, asset, nonce, challenge duration, and approved signature validators. + +### States + +A state represents the current agreed asset allocations. Contains two ledgers (home and non-home), a version number, and a transition describing the operation that produced it. + +### State Advancement + +User and node advance states off-chain by exchanging signed transitions. Each new state MUST have version = previous + 1. Transitions include: deposit, withdrawal, transfer, commit, release, escrow operations, migration. + +### State Enforcement + +Any party MAY submit the latest signed state to the blockchain. The blockchain validates signatures, version ordering, and ledger invariants before accepting. + +### App Sessions + +Multi-party extensions on top of channels with quorum-based governance. Support custom application logic (gaming, escrow, multi-party coordination). Use weight-based voting: each participant has a signature weight, operations require meeting a quorum threshold. + +--- + +## 3. Wire Format + +Communication uses JSON-RPC over WebSocket. + +### Message Envelope + +The outer key (`req`, `res`) indicates message type. The inner tuple is a 4-element ordered array: + +`[RequestId, Method, Payload, Timestamp]` + +| Field | Description | +|-------|-------------| +| RequestId | Numeric identifier unique per connection | +| Method | Operation name (e.g., "channels.v1.submit_state", "node.v1.ping") | +| Payload | Method-specific params (object or array) | +| Timestamp | Unix milliseconds | + +### Request Format +```json +{ "req": [REQUEST_ID, METHOD, PARAMS, TIMESTAMP], "sig": ["SIGNATURE"] } +``` + +### Response Format +```json +{ "res": [REQUEST_ID, METHOD, DATA, TIMESTAMP], "sig": ["SIGNATURE"] } +``` + +The `sig` field contains the sender's signature over the entire `req` or `res` tuple. + +--- + +## 4. Request Signing + +Every v1 RPC request includes a `sig` field — the client's signature over the entire `req` tuple. This is the authorization mechanism. There is no separate authentication handshake in v1; request signatures are the identity proof. + +### Session Keys + +Session keys enable delegated signing with scoped permissions. They are registered and managed via `channels.v1.submit_session_key_state` and `app_sessions.v1.submit_session_key_state`. Session keys have: +- Per-asset allowances with spending caps +- Expiration timestamps +- Scoping to specific applications and app sessions + +--- + +## 5. Channel Lifecycle + +### Definition Parameters + +| Field | Description | +|-------|-------------| +| User | User participant address | +| Node | Node participant address | +| Asset | Asset identifier | +| Nonce | Unique nonce for the channel | +| ChallengeDuration | Challenge period in seconds | +| ApprovedSignatureValidators | Bitmask of approved validation modes | + +### Lifecycle + +1. **Create** — Off-chain: validate, construct initial state, sign +2. **Advance** — Off-chain: exchange signed state transitions (deposit, withdraw, transfer) +3. **Checkpoint** — Submit mutually signed state to blockchain +4. **Challenge** — On-chain dispute: submit state, wait challenge duration +5. **Close** — Off-chain mutual agreement OR on-chain post-challenge + +### Transition Types + +- **Local:** HomeDeposit, HomeWithdrawal, Finalize +- **Transfer:** TransferSend, TransferReceive, Acknowledgement +- **Extension:** Commit, Release (bridge to app sessions) +- **Escrow:** EscrowDepositInitiate/Finalize, EscrowWithdrawalInitiate/Finalize (cross-chain deposit/withdrawal) +- **Migration:** InitiateMigration, FinalizeMigration (move channel's home chain to another blockchain) + +--- + +## 6. State Invariants + +- **Fund conservation:** UserAllocation + NodeAllocation == UserNetFlow + NodeNetFlow +- **Non-negativity:** All allocation values MUST be >= 0 +- **Version ordering:** Off-chain: version = previous + 1. On-chain: submitted > current +- **Version uniqueness:** No two different states with the same version can exist for the same channel +- **Signature requirements:** Mutually signed (user + node) = enforceable. Node-only = NOT enforceable. +- **Locked funds:** UserAllocation + NodeAllocation == LockedFunds (unless closing) +- **Allocation backing:** Sum of allocations must equal locked collateral +- **No retrogression:** State with version <= lastEnforcedVersion cannot be enforced on-chain +- **Cross-deployment replay protection:** ChannelHub VERSION encoded as first byte of channelId + +For the full list of protocol and smart contract invariants, see `contracts/SECURITY.md`. + +--- + +## 7. Nitronode V1 RPC Methods + +Canonical reference: `docs/api.yaml`. Methods are grouped by domain with `v1` versioning. + +### channels.v1 + +| Method | Description | +|--------|-------------| +| `get_home_channel` | Retrieve home channel for a wallet + asset | +| `get_escrow_channel` | Retrieve escrow channel by ID | +| `get_channels` | List all channels for a user with optional filtering | +| `get_latest_state` | Retrieve latest state for a wallet + asset | +| `get_states` | Retrieve multiple states with filtering | +| `request_creation` | Request channel creation (returns signed initial state) | +| `submit_state` | Submit a signed state transition (deposit, withdraw, transfer, close, resize) | +| `submit_session_key_state` | Register/update a channel session key | +| `get_last_key_states` | Get latest channel session key states | + +### app_sessions.v1 + +| Method | Description | +|--------|-------------| +| `create_app_session` | Create a new app session between participants | +| `submit_app_state` | Submit app state update (Operate, Withdraw, or Close intent) | +| `submit_deposit_state` | Submit a deposit into an app session | +| `rebalance_app_sessions` | Atomic rebalancing across multiple app sessions | +| `get_app_definition` | Retrieve app definition for a session | +| `get_app_sessions` | List app sessions with optional filtering | +| `submit_session_key_state` | Register/update an app session key | +| `get_last_key_states` | Get latest app session key states | + +### apps.v1 + +| Method | Description | +|--------|-------------| +| `get_apps` | List registered applications | +| `submit_app_version` | Register a new application (owner must sign packed app data) | + +### user.v1 + +| Method | Description | +|--------|-------------| +| `get_balances` | Retrieve user balances | +| `get_transactions` | Retrieve transaction history with filtering | +| `get_action_allowances` | Retrieve action allowances based on staking level | + +### node.v1 + +| Method | Description | +|--------|-------------| +| `ping` | Connectivity check | +| `get_config` | Node configuration and supported blockchains | +| `get_assets` | Supported assets (optionally filtered by blockchain) | + +> There is no `close_app_session` method. App sessions are closed via `submit_app_state` with Close intent. +> Channel operations (transfer, close, resize) are all handled via `submit_state` with different transition types. + +--- + +## 8. TypeScript SDK (`@yellow-org/sdk`) + +Package: `@yellow-org/sdk` on npm. Uses viem, decimal.js, async/await. + +### Client Creation + +```typescript +import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; + +const { stateSigner, txSigner } = createSigners(privateKey); +const client = await Client.create( + nitronodeUrl, + stateSigner, + txSigner, + withBlockchainRPC(chainId, rpcUrl), +); +``` + +### Key Methods + +| Method | Description | +|--------|-------------| +| `deposit(chainId, asset, amount: Decimal)` | Deposit funds (creates channel if needed) | +| `transfer(recipient, asset, amount: Decimal)` | Off-chain instant transfer | +| `checkpoint(asset)` | Submit co-signed state to blockchain (required after deposit, withdraw, close) | +| `closeHomeChannel(asset)` | Prepare finalize state (follow with checkpoint) | +| `approveToken(chainId, asset, amount: Decimal)` | Approve token spending (ERC-20 only) | +| `getChannels(wallet, options?)` | List channels for a wallet | +| `getBalances(wallet)` | Get account balances | +| `getConfig()` | Get nitronode configuration | +| `createAppSession(definition, sessionData, quorumSigs)` | Create app session | +| `submitAppState(appStateUpdate, quorumSigs)` | Submit app state (Operate, Withdraw, or Close intent) | +| `submitAppSessionDeposit(appStateUpdate, quorumSigs, asset, amount)` | Deposit into app session | +| `rebalanceAppSessions(signedUpdates)` | Atomic rebalancing across multiple app sessions | + +### Security Token Locking (on-chain escrow) + +| Method | Description | +|--------|-------------| +| `escrowSecurityTokens(targetWallet, chainId, amount)` | Lock tokens in the Locking contract | +| `initiateSecurityTokensWithdrawal(chainId)` | Initiate unlock of escrowed tokens | +| `cancelSecurityTokensWithdrawal(chainId)` | Cancel pending unlock | +| `withdrawSecurityTokens(chainId, destination)` | Withdraw unlocked tokens | +| `approveSecurityToken(chainId, amount)` | Approve Locking contract to spend tokens | +| `getLockedBalance(chainId, wallet)` | Get locked balance for a wallet | +| `getEscrowChannel(escrowChannelId)` | Get escrow channel info | + +> Note: There is no `closeAppSession()` on the SDK Client. Close a session by calling `submitAppState` with Close intent. + +### Compat Layer (`@yellow-org/sdk-compat`) + +The compat layer (`@yellow-org/sdk-compat`) bridges the gap between the 0.5.x API surface and the v1 protocol SDK. It exists because dApps built on the old `@layer-3/nitrolite` v0.5.3 package used a different API shape: `NitroliteClient` instead of `Client.create`, legacy method names like `transfer`/`create_channel`/`close_app_session` instead of v1 grouped methods, and different type structures. + +The compat layer wraps the v1 `Client` internally but exposes the old 0.5.x API surface, so existing dApps can upgrade the underlying SDK without rewriting all their code at once. It re-exports legacy types (`RPCMethod`, `RPCChannelStatus`, `NitroliteRPCMessage`) and provides helper functions (`createTransferMessage`, `createAuthRequestMessage`) that map to v1 calls internally. + +**For new projects, always use `@yellow-org/sdk` directly.** The compat layer is for migration only. + +--- + +## 9. Go SDK (`github.com/layer-3/nitrolite/sdk/go`) + +Uses context.Context, (T, error) tuples, github.com/shopspring/decimal. + +### Client Creation + +```go +import ( + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" +) + +stateSigner, _ := sign.NewEthereumMsgSigner(privateKey) +txSigner, _ := sign.NewEthereumRawSigner(privateKey) + +client, err := sdk.NewClient(nitronodeURL, stateSigner, txSigner, + sdk.WithBlockchainRPC(chainID, rpcURL), +) +defer client.Close() +``` + +### Key Methods + +| Method | Description | +|--------|-------------| +| `Deposit(ctx, chainID, asset, amount)` | Deposit funds (creates channel if needed) | +| `Transfer(ctx, recipient, asset, amount)` | Off-chain transfer | +| `Checkpoint(ctx, asset)` | Submit co-signed state to blockchain (required after deposit, withdraw, close) | +| `CloseHomeChannel(ctx, asset)` | Prepare finalize state (follow with Checkpoint) | +| `ApproveToken(ctx, chainID, asset, amount)` | Approve token spending (ERC-20 only) | +| `GetChannels(ctx, wallet, options)` | List channels for a wallet | +| `GetBalances(ctx, wallet)` | Get balances | +| `GetConfig(ctx)` | Get nitronode config | +| `CreateAppSession(ctx, def, sessionData, quorumSigs)` | Create app session | +| `SubmitAppState(ctx, appStateUpdate, quorumSigs)` | Submit app state (Operate, Withdraw, or Close intent) | +| `SubmitAppSessionDeposit(ctx, appStateUpdate, quorumSigs, asset, amount)` | Deposit into app session | +| `RebalanceAppSessions(ctx, signedUpdates)` | Atomic rebalancing across multiple app sessions | + +### Security Token Locking (on-chain escrow) + +| Method | Description | +|--------|-------------| +| `EscrowSecurityTokens(ctx, targetWallet, chainID, amount)` | Lock tokens in the Locking contract | +| `InitiateSecurityTokensWithdrawal(ctx, chainID)` | Initiate unlock | +| `CancelSecurityTokensWithdrawal(ctx, chainID)` | Cancel pending unlock | +| `WithdrawSecurityTokens(ctx, chainID, destination)` | Withdraw unlocked tokens | +| `ApproveSecurityToken(ctx, chainID, amount)` | Approve Locking contract spend | +| `GetLockedBalance(ctx, chainID, wallet)` | Get locked balance | +| `GetEscrowChannel(ctx, escrowChannelID)` | Get escrow channel info | + +> Note: There is no `CloseAppSession()` on the Go Client. Close a session by calling `SubmitAppState` with Close intent. + +### Two-Step Pattern for Blockchain Operations + +1. Build and co-sign state off-chain (Deposit, Withdraw, etc.) +2. Settle on-chain via Checkpoint + +--- + +## 10. Smart Contracts + +Foundry project. Key contracts: + +| Contract | Purpose | +|----------|---------| +| `ChannelHub.sol` | Main entry point — creates channels, processes checkpoints | +| `ChannelEngine.sol` | State validation, transition processing | +| `EscrowDepositEngine.sol` | Cross-chain deposit escrow | +| `EscrowWithdrawalEngine.sol` | Cross-chain withdrawal escrow | +| `SessionKeyValidator.sol` | Validates session key signatures | +| `ECDSAValidator.sol` | Validates standard ECDSA signatures | + +### Build and Test + +```bash +cd contracts && forge build # Compile +cd contracts && forge test # Run tests +cd contracts && forge fmt # Format +``` + +--- + +## 11. Repository Structure + +| Directory | Description | Language | +|-----------|-------------|----------| +| `contracts/` | Solidity smart contracts (Foundry) | Solidity | +| `nitronode/` | Off-chain broker: WebSocket JSON-RPC | Go | +| `sdk/ts/` | TypeScript SDK (`@yellow-org/sdk`) | TypeScript | +| `sdk/ts-compat/` | Compat layer for v0.5.3 migration | TypeScript | +| `sdk/go/` | Go SDK | Go | +| `sdk/mcp/` | Unified MCP server for AI agents | TypeScript | +| `cerebro/` | Interactive CLI for channel management | Go | +| `pkg/` | Shared Go packages (core, sign, rpc, app) | Go | +| `docs/` | Protocol specifications | Markdown | + +--- + +## 12. Common Patterns + +### TypeScript: Transfer with Compat Layer + +```typescript +import { NitroliteClient } from '@yellow-org/sdk-compat'; +const client = new NitroliteClient(url, stateSigner, txSigner, options); +await client.transfer(recipient, 'usdc', '5.0'); +``` + +### Go: Full Transfer Flow + +```go +ctx := context.Background() +client.Deposit(ctx, 11155111, "usdc", decimal.NewFromInt(10)) +client.Checkpoint(ctx, "usdc") // settle deposit on-chain +client.Transfer(ctx, recipient, "usdc", decimal.NewFromInt(5)) +client.CloseHomeChannel(ctx, "usdc") +client.Checkpoint(ctx, "usdc") // settle close on-chain +``` + +### App Session: 2-of-2 Quorum + +Both SDKs support app sessions with weight-based quorum: +- Participants: [{addr1, weight: 50}, {addr2, weight: 50}], Quorum: 100 +- Both must sign to authorize state updates +- Variations: 2-of-3 (weights [50,50,50], quorum 100), weighted operator (weights [70,30], quorum 70) diff --git a/llms.txt b/llms.txt new file mode 100644 index 000000000..d9c4f4140 --- /dev/null +++ b/llms.txt @@ -0,0 +1,46 @@ +# Nitrolite + +> State channel protocol for Ethereum and EVM-compatible blockchains. Instant off-chain transactions with on-chain security guarantees. + +## Documentation + +- [Protocol Overview](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/overview.md): Design goals, system roles, core concepts +- [Terminology](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/terminology.md): Canonical definitions of all protocol terms +- [State Model](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/state-model.md): State structure, versioning, consistency rules +- [Channel Protocol](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/channel-protocol.md): Channel lifecycle, transitions, advancement rules +- [Cryptography](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/cryptography.md): Encoding, hashing, signing, replay protection +- [Enforcement](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/enforcement.md): On-chain checkpoints, validation, dispute resolution +- [Interactions](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/interactions.md): Message envelope, wire format, operations +- [Security](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/security-and-limitations.md): Security guarantees, trust assumptions, known limitations +- [Cross-Chain](https://github.com/layer-3/nitrolite/blob/main/docs/protocol/cross-chain-and-assets.md): Unified asset model, cross-chain operations + +## SDKs + +- [TypeScript SDK](https://github.com/layer-3/nitrolite/tree/main/sdk/ts): `@yellow-org/sdk` — async/await, viem, decimal.js +- [TypeScript Compat](https://github.com/layer-3/nitrolite/tree/main/sdk/ts-compat): `@yellow-org/sdk-compat` — migration bridge from v0.5.3 +- [Go SDK](https://github.com/layer-3/nitrolite/tree/main/sdk/go): `github.com/layer-3/nitrolite/sdk/go` — context.Context, (T, error) tuples + +## Nitronode API + +- [API Reference](https://github.com/layer-3/nitrolite/blob/main/docs/legacy/API.md): All RPC methods with request/response examples +- [Protocol Spec](https://github.com/layer-3/nitrolite/blob/main/docs/legacy/Clearnode.protocol.md): Auth flow, channel creation, app sessions +- [Entities](https://github.com/layer-3/nitrolite/blob/main/docs/legacy/Entities.md): Data entity definitions +- [Session Keys](https://github.com/layer-3/nitrolite/blob/main/docs/legacy/SessionKeys.md): Delegated signing with spending caps + +## Smart Contracts + +- [Contracts Source](https://github.com/layer-3/nitrolite/tree/main/contracts/src): ChannelHub, ChannelEngine, EscrowDepositEngine, signature validators +- [Contract Design](https://github.com/layer-3/nitrolite/blob/main/contracts/suggested-contract-design.md): Architecture rationale and unified transition pattern + +## AI Tooling + +- [TypeScript MCP Server](https://github.com/layer-3/nitrolite/tree/main/sdk/ts-mcp): MCP server for TS SDK + protocol knowledge (22 resources, 8 tools, 3 prompts) +- [Go MCP Server](https://github.com/layer-3/nitrolite/tree/main/sdk/mcp-go): MCP server for Go SDK + protocol knowledge (16 resources, 5 tools, 2 prompts) + +## Key Concepts + +- **State Channels**: Off-chain state containers between a user and a node. Signed state updates exchanged off-chain; any party can enforce on-chain. +- **Nitronode** (formerly Clearnode): Off-chain broker that manages channels, processes RPC requests, and syncs with blockchain. +- **Wire Format**: JSON-RPC over WebSocket. Request: `{ "req": [REQUEST_ID, METHOD, PARAMS, TIMESTAMP], "sig": ["SIGNATURE"] }` +- **Authorization**: Every RPC request is signed via the `sig` field. Session keys enable delegated signing with spending caps. +- **App Sessions**: Multi-party extensions on top of channels with quorum-based governance and custom application logic. diff --git a/clearnode/.gitignore b/nitronode/.gitignore similarity index 100% rename from clearnode/.gitignore rename to nitronode/.gitignore diff --git a/clearnode/Dockerfile b/nitronode/Dockerfile similarity index 50% rename from clearnode/Dockerfile rename to nitronode/Dockerfile index f52a3e866..84e82cec9 100644 --- a/clearnode/Dockerfile +++ b/nitronode/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25-alpine AS builder +FROM golang:1.25.9-alpine AS builder WORKDIR /build ENV CGO_ENABLED=1 @@ -11,17 +11,17 @@ RUN go mod download COPY . . -RUN go build -o ./bin/clearnode -ldflags "-X main.Version=${VERSION}" ./clearnode +RUN go build -o ./bin/nitronode -ldflags "-X main.Version=${VERSION}" ./nitronode FROM alpine:3.23.3 -RUN addgroup -g 1001 -S clearnode -RUN adduser -S clearnode -u 1001 -G clearnode +RUN addgroup -g 1001 -S nitronode +RUN adduser -S nitronode -u 1001 -G nitronode -USER clearnode +USER nitronode COPY --from=builder /build/bin /bin EXPOSE 7824 4242 -CMD ["clearnode"] +CMD ["nitronode"] diff --git a/clearnode/README.md b/nitronode/README.md similarity index 65% rename from clearnode/README.md rename to nitronode/README.md index be9da2368..a8976728a 100644 --- a/clearnode/README.md +++ b/nitronode/README.md @@ -1,10 +1,10 @@ -# Clearnode +# Nitronode -Clearnode is the off-chain node implementation for the Nitrolite protocol. It manages state channels, processes off-chain transactions, and coordinates state updates between users and applications to enable fast, low-cost payment channels and complex application sessions. +Nitronode (formerly Clearnode) is the off-chain node implementation for the Nitrolite protocol. It manages state channels, processes off-chain transactions, and coordinates state updates between users and applications to enable fast, low-cost payment channels and complex application sessions. ## Overview -Clearnode provides a WebSocket-based RPC service that allows users and applications to: +Nitronode provides a WebSocket-based RPC service that allows users and applications to: - Create and manage home and escrow channels on multiple blockchains - Perform instant off-chain transfers between users - Execute multi-party application sessions with arbitrary logic @@ -16,7 +16,7 @@ The node monitors blockchain events, validates state transitions using a monoton ## Architecture -Clearnode is built with a modular architecture: +Nitronode is built with a modular architecture: - **RPC Server**: WebSocket-based JSON-RPC server handling client requests. - **Blockchain Listeners**: Monitors on-chain events from Nitrolite `ChannelHub` contracts across multiple chains. @@ -40,7 +40,7 @@ For detailed API specifications, see [../docs/api.yaml](../docs/api.yaml). ## Configuration -Clearnode uses YAML files for core configuration and environment variables for sensitive data and runtime overrides. +Nitronode uses YAML files for core configuration and environment variables for sensitive data and runtime overrides. ### Blockchain Configuration @@ -83,13 +83,14 @@ assets: | Variable | Description | Default | |----------|-------------|---------| -| `CLEARNODE_SIGNER_KEY` | Private key for signing node state updates | (Required) | -| `CLEARNODE_DATABASE_DRIVER` | `sqlite` or `postgres` | `sqlite` | -| `CLEARNODE_DATABASE_URL` | Connection string or file path | `clearnode.db` | -| `CLEARNODE_LOG_LEVEL` | `debug`, `info`, `warn`, `error` | `info` | -| `CLEARNODE_BLOCKCHAIN_RPC_` | RPC endpoint for a specific blockchain | (Required) | +| `NITRONODE_SIGNER_KEY` | Private key for signing node state updates | (Required) | +| `NITRONODE_DATABASE_DRIVER` | `sqlite` or `postgres` | `sqlite` | +| `NITRONODE_DATABASE_URL` | Postgres DSN/URL or sqlite file path. When set for `postgres`, used verbatim and overrides the individual host/user/password/sslmode fields | `nitronode.db` | +| `NITRONODE_DATABASE_SSLMODE` | Postgres SSL mode: `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full` | `require` | +| `NITRONODE_LOG_LEVEL` | `debug`, `info`, `warn`, `error` | `info` | +| `NITRONODE_BLOCKCHAIN_RPC_` | RPC endpoint for a specific blockchain | (Required) | -## Running Clearnode +## Running Nitronode ### Prerequisites @@ -101,8 +102,8 @@ assets: 1. Set up your configuration files in a `./config` directory. 2. Set the required environment variables: ```bash - export CLEARNODE_SIGNER_KEY=0x... - export CLEARNODE_BLOCKCHAIN_RPC_POLYGON_AMOY=https://... + export NITRONODE_SIGNER_KEY=0x... + export NITRONODE_BLOCKCHAIN_RPC_POLYGON_AMOY=https://... ``` 3. Run the node: ```bash @@ -114,8 +115,8 @@ The node will be available at `ws://localhost:7824/ws`. ### Docker ```bash -docker build -t clearnode . -docker run -p 7824:7824 -e CLEARNODE_SIGNER_KEY=... clearnode +docker build -t nitronode . +docker run -p 7824:7824 -e NITRONODE_SIGNER_KEY=... nitronode ``` ## Development @@ -123,7 +124,7 @@ docker run -p 7824:7824 -e CLEARNODE_SIGNER_KEY=... clearnode ### Project Structure ``` -clearnode/ +nitronode/ ├── api/ # JSON-RPC request handlers ├── config/ # Default configurations and migrations ├── event_handlers/ # Logic for reacting to blockchain events @@ -140,6 +141,16 @@ clearnode/ export GOCACHE=/tmp/gocache && go test -v ./... ``` +## Protocol Features Not Yet Active + +The following protocol operations are fully specified in [protocol-description.md](../protocol-description.md) and implemented in the `ChannelHub` smart contract, but are **not yet active in the Nitronode off-chain implementation**. Submitting these transition types via `channels.v1.submit_state` returns an error. + +| Feature | Transition types | Status | +|---------|-----------------|--------| +| Home chain migration | `migrate` | Off-chain flow not implemented. On-chain `initiateMigration()` / `finalizeMigration()` are functional. Event handlers for `MigrationInInitiated`, `MigrationOutFinalized`, etc. are stubs. | +| Cross-chain escrow deposit | `mutual_lock`, `escrow_lock`, `escrow_deposit` | Off-chain flow implemented, but not fully tested. | +| Cross-chain escrow withdrawal | `escrow_withdraw` | Off-chain flow implemented, but not fully tested. | + ## Documentation - [Nitrolite Protocol Overview](../protocol-description.md) diff --git a/clearnode/action_gateway/action_gateway.go b/nitronode/action_gateway/action_gateway.go similarity index 85% rename from clearnode/action_gateway/action_gateway.go rename to nitronode/action_gateway/action_gateway.go index 1eecbf519..9cc6f42b1 100644 --- a/clearnode/action_gateway/action_gateway.go +++ b/nitronode/action_gateway/action_gateway.go @@ -74,24 +74,6 @@ func NewActionGatewayFromYaml(configDirPath string) (*ActionGateway, error) { 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 diff --git a/clearnode/action_gateway/action_gateway_test.go b/nitronode/action_gateway/action_gateway_test.go similarity index 100% rename from clearnode/action_gateway/action_gateway_test.go rename to nitronode/action_gateway/action_gateway_test.go diff --git a/nitronode/action_gateway/interface.go b/nitronode/action_gateway/interface.go new file mode 100644 index 000000000..582043d6b --- /dev/null +++ b/nitronode/action_gateway/interface.go @@ -0,0 +1,41 @@ +package action_gateway + +import ( + "time" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" +) + +// ActionAllower defines the interface for action gating and allowance checks. +type ActionAllower interface { + // AllowAction checks if a user is allowed to perform a specific gated action + // based on their past activity and allowances. + AllowAction(tx Store, userAddress string, gatedAction core.GatedAction) error + + // AllowAppRegistration checks if a user is allowed to register a new application + // based on their staked tokens and existing app count. + AllowAppRegistration(tx Store, userAddress string) error + + // GetUserAllowances returns user allowance for every gated action. + // An empty slice indicates the user has no limits. + GetUserAllowances(tx Store, userAddress string) ([]core.ActionAllowance, error) +} + +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) +} diff --git a/nitronode/action_gateway/permissive_action_allower.go b/nitronode/action_gateway/permissive_action_allower.go new file mode 100644 index 000000000..a7044c099 --- /dev/null +++ b/nitronode/action_gateway/permissive_action_allower.go @@ -0,0 +1,23 @@ +package action_gateway + +import "github.com/layer-3/nitrolite/pkg/core" + +// PermissiveActionAllower is an ActionAllower that allows all actions without any checks. +// It returns empty user allowances, indicating the user is not limited. +type PermissiveActionAllower struct{} + +func NewPermissiveActionAllower() *PermissiveActionAllower { + return &PermissiveActionAllower{} +} + +func (p *PermissiveActionAllower) AllowAction(_ Store, _ string, _ core.GatedAction) error { + return nil +} + +func (p *PermissiveActionAllower) AllowAppRegistration(_ Store, _ string) error { + return nil +} + +func (p *PermissiveActionAllower) GetUserAllowances(_ Store, _ string) ([]core.ActionAllowance, error) { + return []core.ActionAllowance{}, nil +} diff --git a/clearnode/api/app_session_v1/README.md b/nitronode/api/app_session_v1/README.md similarity index 93% rename from clearnode/api/app_session_v1/README.md rename to nitronode/api/app_session_v1/README.md index 81545151b..987353ace 100644 --- a/clearnode/api/app_session_v1/README.md +++ b/nitronode/api/app_session_v1/README.md @@ -6,7 +6,7 @@ This directory contains the V1 API handlers for app session management, implemen ## Architecture -### API Layer (`clearnode/api/app_session_v1`) +### API Layer (`nitronode/api/app_session_v1`) - **Thin RPC handlers** that parse requests and format responses - Delegates all business logic to `pkg/app` - **Separate file per endpoint** (following channel_v1 pattern): @@ -672,7 +672,12 @@ ORDER BY created_at DESC; ### 7. `app_sessions.v1.submit_session_key_state` -**Purpose**: Submits a session key state for registration or update. Session keys allow delegated signing for app sessions, enabling applications to sign on behalf of a user's wallet. +**Purpose**: Submits a session key state for registration, rotation/update, or revocation. Session keys allow delegated signing for app sessions, enabling applications to sign on behalf of a user's wallet. + +**Submit semantics**: +- **Registration**: first submit for a `(user, session_key)` pair (version=1, future `expires_at`). +- **Rotation/update**: bump version with a future `expires_at` to change scopes or extend lifetime. +- **Revocation**: bump version with `expires_at <= now`. The auth path stops accepting state signed by the key and the slot is freed against the per-user cap. **Key Features**: - Versioned session key states (each update increments the version) @@ -704,14 +709,20 @@ ORDER BY created_at DESC; - `user_address` must be a valid hex address - `session_key` must be a valid hex address - `version` must be greater than 0 -- `expires_at` must be in the future +- `expires_at` may be in the past — past values express revocation (the key is retired and the slot is freed) - `user_sig` is required +- `application_ids` entries must be lowercase strings (non-lowercase values are rejected before signature verification) +- `app_session_ids` entries must be lowercase strings (non-lowercase values are rejected before signature verification) - Version must be sequential (latest_version + 1) - Signature must recover to `user_address` +- The per-user cap (`NITRONODE_MAX_SESSION_KEYS_PER_USER`, default 100) is enforced whenever the submit transitions the slot from inactive to active: a brand-new key (no prior state) or a reactivation (previous latest state's `expires_at` was already in the past). Rotation/update against a still-active key, and revocation submits, are not subject to the cap. + +**Concurrency**: A `SELECT ... FOR UPDATE` is taken on a per-(user, session_key, kind) pointer row in `current_session_key_states_v1` so concurrent submits for the same key serialize and report a clean "expected version" error instead of racing on the history table's UNIQUE constraint. **Signature Verification**: - Uses ABI encoding via `PackAppSessionKeyStateV1` to create a deterministic hash - Encodes: user_address (address), session_key (address), version (uint64), application_ids (bytes32[]), app_session_ids (bytes32[]), expires_at (uint64) +- Each application_id and app_session_id string is converted to bytes32 via `keccak256(utf8(id))` before ABI encoding - The `user_sig` field is excluded from packing (it is the signature itself) - Recovers signer address from ECDSA signature - Validates that recovered address matches `user_address` @@ -750,11 +761,15 @@ ORDER BY created_at DESC; - Returns only the latest version per session key - Excludes expired session key states +**Pagination**: Optional `pagination` block (`limit`, `offset`); response includes a `pagination` block with `current_page`, `page_count`, `per_page`, `total_items`. Server-side default and max `limit` are both 10. + +**Read path**: Filters `current_session_key_states_v1` by (user_address, kind=app_session) and JOINs the history table on (user_address, session_key, version). Per-request DB work is bounded by the number of distinct session keys for the user, regardless of version churn in history. + ## Implementation Details ### Files -**API Layer** (`clearnode/api/app_session_v1/`): +**API Layer** (`nitronode/api/app_session_v1/`): - `handler.go` - Handler struct with signature validators and signer - `create_app_session.go` - Create app session endpoint handler - `submit_deposit_state.go` - Submit deposit state endpoint handler @@ -821,6 +836,7 @@ The implementation uses Ethereum ABI encoding for deterministic hashing and sign #### `PackAppSessionKeyStateV1(state AppSessionKeyStateV1) ([]byte, error)` - Packs session key state for signature verification using ABI encoding - Encodes: user_address (address), session_key (address), version (uint64), application_ids (bytes32[]), app_session_ids (bytes32[]), expires_at (uint64) +- Each `application_id` and `app_session_id` string is converted to bytes32 via `keccak256(utf8(id))`, providing deterministic, collision-resistant identifiers in practice - Excludes the `user_sig` field (it is the signature itself) - Returns Keccak256 hash of ABI-encoded data - Used in `submit_session_key_state` to verify the user's signature @@ -880,7 +896,7 @@ type Store interface { StoreAppSessionKeyState(state app.AppSessionKeyStateV1) error GetLastAppSessionKeyVersion(wallet, sessionKey string) (uint64, error) GetLastAppSessionKeyStates(wallet string, sessionKey *string) ([]app.AppSessionKeyStateV1, error) - GetAppSessionKeyOwner(sessionKey, appSessionId string) (string, error) + GetAppSessionKeyOwner(sessionKey, appSessionId, applicationId string) (string, error) } ``` @@ -1005,7 +1021,7 @@ The implementation includes comprehensive test coverage: ```bash # Run all tests -cd clearnode/api/app_session_v1 +cd nitronode/api/app_session_v1 go test -v # Run specific test suites @@ -1023,4 +1039,4 @@ go test -v -run TestCreateAppSession_.* # All create_app_session tests - RPC Types: `pkg/rpc/` - Application Types: `pkg/app/` - Core Package: `pkg/core/` -- Channel V1 Reference: `clearnode/api/channel_v1/` +- Channel V1 Reference: `nitronode/api/channel_v1/` diff --git a/clearnode/api/app_session_v1/create_app_session.go b/nitronode/api/app_session_v1/create_app_session.go similarity index 65% rename from clearnode/api/app_session_v1/create_app_session.go rename to nitronode/api/app_session_v1/create_app_session.go index 1db8d74dd..c00973c9e 100644 --- a/clearnode/api/app_session_v1/create_app_session.go +++ b/nitronode/api/app_session_v1/create_app_session.go @@ -41,6 +41,10 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { c.Fail(nil, "application id is required") return } + if !app.IsValidApplicationID(reqPayload.Definition.Application) { + c.Fail(rpc.Errorf("invalid application id: must match %s", app.ApplicationIDRegex.String()), "") + return + } appDef, err := unmapAppDefinitionV1(reqPayload.Definition) if err != nil { @@ -55,8 +59,8 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { "nonce", reqPayload.Definition.Nonce) // Validate nonce - if reqPayload.Definition.Nonce == "" || reqPayload.Definition.Nonce == "0" { - c.Fail(nil, "nonce is zero or not provided") + if appDef.Nonce == 0 { + c.Fail(nil, "nonce must be non-zero") return } @@ -66,11 +70,19 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { return } - // Validate quorum against total weights and check for duplicate participants + // Validate quorum against total weights and check for duplicate participants. + // Normalize each participant's wallet so the deduplication, packing, app session ID + // derivation, and stored participant list all see the canonical (lowercase, 0x-prefixed) + // representation regardless of how the caller cased the address or whether they + // included the 0x prefix. var totalWeights uint8 participantWeights := make(map[string]uint8) - for _, participant := range reqPayload.Definition.Participants { - participantWallet := strings.ToLower(participant.WalletAddress) + for i, participant := range reqPayload.Definition.Participants { + participantWallet, err := core.NormalizeHexAddress(participant.WalletAddress) + if err != nil { + c.Fail(rpc.Errorf("invalid participant wallet_address %q: %v", participant.WalletAddress, err), "") + return + } // Check for duplicate participant addresses if _, exists := participantWeights[participantWallet]; exists { @@ -79,6 +91,7 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { } totalWeights += participant.SignatureWeight participantWeights[participantWallet] = participant.SignatureWeight + appDef.Participants[i].WalletAddress = participantWallet } if reqPayload.Definition.Quorum > totalWeights { @@ -108,50 +121,56 @@ 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 h.appRegistryEnabled { + registeredApp, err := tx.GetApp(appDef.ApplicationID) if err != nil { - return rpc.Errorf("failed to decode signature: %v", err) + return rpc.Errorf("failed to look up application: %v", err) } - if len(sigBytes) == 0 { - return rpc.Errorf("empty owner_sig after decode") + + // App must be registered regardless of CreationApprovalNotRequired flag. + if registeredApp == nil { + return rpc.Errorf("application %s is not registered", appDef.ApplicationID) } - 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) + 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, appDef.ApplicationID) + }, + ) + 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) + } } - 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) } } - err = h.actionGateway.AllowAction(tx, registeredApp.App.OwnerWallet, core.GatedActionAppSessionCreation) - if err != nil { - return rpc.NewError(err) + if err := h.verifyQuorum(tx, appSessionID, appDef.ApplicationID, participantWeights, appDef.Quorum, packedRequest, reqPayload.QuorumSigs); err != nil { + return err } // Create app session with 0 allocations @@ -172,10 +191,6 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { return rpc.Errorf("failed to create app session: %v", err) } - if err := h.verifyQuorum(tx, appSessionID, appDef.ApplicationID, participantWeights, appDef.Quorum, packedRequest, reqPayload.QuorumSigs); err != nil { - return err - } - return nil }) diff --git a/clearnode/api/app_session_v1/create_app_session_test.go b/nitronode/api/app_session_v1/create_app_session_test.go similarity index 80% rename from clearnode/api/app_session_v1/create_app_session_test.go rename to nitronode/api/app_session_v1/create_app_session_test.go index ccbab0721..db4681c14 100644 --- a/clearnode/api/app_session_v1/create_app_session_test.go +++ b/nitronode/api/app_session_v1/create_app_session_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" @@ -22,7 +22,7 @@ func TestCreateAppSession_Success(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -34,8 +34,9 @@ func TestCreateAppSession_Success(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create a real test wallet for participant1 @@ -126,7 +127,7 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -138,8 +139,9 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create real test wallets for participant1 and participant2 @@ -221,67 +223,70 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { } func TestCreateAppSession_ZeroNonce(t *testing.T) { - // Setup - 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, - ) - - // Test data - participant1 := "0x1111111111111111111111111111111111111111" - - reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ - Definition: rpc.AppDefinitionV1{ - Application: "test-app", - Participants: []rpc.AppParticipantV1{ - { - WalletAddress: participant1, - SignatureWeight: 1, + // Zero-padded values must also be rejected: strconv.ParseUint accepts "00", + // "000", etc. and yields 0, which used to bypass the raw-string "0" check. + cases := []string{"0", "00", "000"} + for _, nonce := range cases { + t.Run("nonce="+nonce, func(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + participant1 := "0x1111111111111111111111111111111111111111" + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "test-app", + Participants: []rpc.AppParticipantV1{ + { + WalletAddress: participant1, + SignatureWeight: 1, + }, + }, + Quorum: 1, + Nonce: nonce, }, - }, - Quorum: 1, - Nonce: "0", // Zero nonce - invalid - }, - QuorumSigs: []string{"0x1234567890abcdef"}, - } + QuorumSigs: []string{"0x1234567890abcdef"}, + } - // Create RPC context - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), - } + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), + } - handler.CreateAppSession(ctx) + handler.CreateAppSession(ctx) - assert.NotNil(t, ctx.Response) + assert.NotNil(t, ctx.Response) - // Verify response contains error about nonce - err = ctx.Response.Error() - require.Error(t, err) - assert.Contains(t, err.Error(), "nonce") + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "nonce") - // Verify no mocks were called since we fail early - mockStore.AssertExpectations(t) + mockStore.AssertExpectations(t) + }) + } } func TestCreateAppSession_QuorumExceedsTotalWeights(t *testing.T) { @@ -292,7 +297,7 @@ func TestCreateAppSession_QuorumExceedsTotalWeights(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -304,8 +309,9 @@ func TestCreateAppSession_QuorumExceedsTotalWeights(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Test data @@ -362,7 +368,7 @@ func TestCreateAppSession_NoSignatures(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -374,8 +380,9 @@ func TestCreateAppSession_NoSignatures(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Test data @@ -426,7 +433,7 @@ func TestCreateAppSession_SignatureFromNonParticipant(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -438,8 +445,9 @@ func TestCreateAppSession_SignatureFromNonParticipant(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create a wallet that is NOT a participant @@ -476,7 +484,6 @@ func TestCreateAppSession_SignatureFromNonParticipant(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 payload, err := rpc.NewPayload(reqPayload) @@ -508,7 +515,7 @@ func TestCreateAppSession_QuorumNotMet(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -520,8 +527,9 @@ func TestCreateAppSession_QuorumNotMet(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create a real wallet for participant1 @@ -572,7 +580,6 @@ 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 payload, err := rpc.NewPayload(reqPayload) @@ -604,7 +611,7 @@ func TestCreateAppSession_DuplicateSignatures(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -616,8 +623,9 @@ func TestCreateAppSession_DuplicateSignatures(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create a real wallet for participant1 @@ -664,7 +672,6 @@ 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 payload, err := rpc.NewPayload(reqPayload) @@ -697,7 +704,7 @@ func TestCreateAppSession_InvalidSignatureHex(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -709,8 +716,9 @@ func TestCreateAppSession_InvalidSignatureHex(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Test data @@ -734,7 +742,6 @@ func TestCreateAppSession_InvalidSignatureHex(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 payload, err := rpc.NewPayload(reqPayload) @@ -766,7 +773,7 @@ func TestCreateAppSession_SignatureRecoveryFailure(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -778,8 +785,9 @@ func TestCreateAppSession_SignatureRecoveryFailure(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Test data @@ -804,7 +812,6 @@ func TestCreateAppSession_SignatureRecoveryFailure(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 payload, err := rpc.NewPayload(reqPayload) @@ -835,7 +842,7 @@ func TestCreateAppSession_AppNotRegistered(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -847,8 +854,9 @@ func TestCreateAppSession_AppNotRegistered(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -904,7 +912,7 @@ func TestCreateAppSession_OwnerSigRequired(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -916,8 +924,9 @@ func TestCreateAppSession_OwnerSigRequired(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -980,7 +989,7 @@ func TestCreateAppSession_OwnerSigSuccess(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -992,8 +1001,9 @@ func TestCreateAppSession_OwnerSigSuccess(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xnode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create participant and owner wallets @@ -1070,3 +1080,154 @@ func TestCreateAppSession_OwnerSigSuccess(t *testing.T) { mockStore.AssertExpectations(t) } + +// TestCreateAppSession_AppRegistryDisabled verifies that when appRegistryEnabled=false, +// app lookup, owner signature validation, and AllowAction are all skipped. +func TestCreateAppSession_AppRegistryDisabled(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + false, // appRegistryEnabled=false + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + participant2 := "0x2222222222222222222222222222222222222222" + + appDef := app.AppDefinitionV1{ + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + {WalletAddress: participant2, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + } + sessionData := `{"test": "data"}` + sig1 := wallet1.SignCreateRequest(t, appDef, sessionData) + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "test-app", + Participants: []rpc.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + {WalletAddress: participant2, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: "12345", + }, + QuorumSigs: []string{sig1}, + SessionData: sessionData, + } + + // Only CreateAppSession should be called — NO GetApp, NO AllowAction + 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) + + // Strict: GetApp and AllowAction must NOT have been called + mockStore.AssertNotCalled(t, "GetApp", mock.Anything) + mockStore.AssertExpectations(t) +} + +// TestCreateAppSession_DuplicateParticipantAcrossCases verifies that two participant +// addresses that differ only in letter case are detected as duplicates. Without address +// normalization the duplicate-check map would key on the raw representation and accept +// the same wallet twice. +func TestCreateAppSession_DuplicateParticipantAcrossCases(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + lower := "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + upper := "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "test-app", + Participants: []rpc.AppParticipantV1{ + {WalletAddress: lower, SignatureWeight: 1}, + {WalletAddress: upper, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: "12345", + }, + QuorumSigs: []string{"0xdeadbeef"}, + } + + 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) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "duplicate participant address") + mockStore.AssertNotCalled(t, "GetApp", mock.Anything) + mockStore.AssertNotCalled(t, "CreateAppSession", mock.Anything) +} diff --git a/clearnode/api/app_session_v1/get_app_definition.go b/nitronode/api/app_session_v1/get_app_definition.go similarity index 91% rename from clearnode/api/app_session_v1/get_app_definition.go rename to nitronode/api/app_session_v1/get_app_definition.go index 849b21a05..2d2cf81b8 100644 --- a/clearnode/api/app_session_v1/get_app_definition.go +++ b/nitronode/api/app_session_v1/get_app_definition.go @@ -14,7 +14,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) { return } - var definition rpc.AppDefinitionV1 + var definition *rpc.AppDefinitionV1 err := h.useStoreInTx(func(store Store) error { session, err := store.GetAppSession(req.AppSessionID) @@ -23,7 +23,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) { } if session == nil { - return rpc.Errorf("app_session_not_found") + return nil } // Convert participants @@ -35,7 +35,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) { } } - definition = rpc.AppDefinitionV1{ + definition = &rpc.AppDefinitionV1{ Application: session.ApplicationID, Participants: participants, Quorum: session.Quorum, diff --git a/clearnode/api/app_session_v1/get_app_definition_test.go b/nitronode/api/app_session_v1/get_app_definition_test.go similarity index 88% rename from clearnode/api/app_session_v1/get_app_definition_test.go rename to nitronode/api/app_session_v1/get_app_definition_test.go index 8f3fb7231..4678f056c 100644 --- a/clearnode/api/app_session_v1/get_app_definition_test.go +++ b/nitronode/api/app_session_v1/get_app_definition_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" @@ -17,7 +17,7 @@ import ( func TestGetAppDefinition_Success(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -84,6 +84,7 @@ func TestGetAppDefinition_Success(t *testing.T) { var response rpc.AppSessionsV1GetAppDefinitionResponse err = ctx.Response.Payload.Translate(&response) require.NoError(t, err) + require.NotNil(t, response.Definition) assert.Equal(t, "game", response.Definition.Application) assert.Len(t, response.Definition.Participants, 2) @@ -101,7 +102,7 @@ func TestGetAppDefinition_Success(t *testing.T) { func TestGetAppDefinition_NotFound(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -143,9 +144,14 @@ func TestGetAppDefinition_NotFound(t *testing.T) { // Execute handler.GetAppDefinition(ctx) - // Assert - assert.NotNil(t, ctx.Response.Error()) - assert.Contains(t, ctx.Response.Error().Error(), "app_session_not_found") + // Assert: absence is a successful response with definition == nil + assert.NotNil(t, ctx.Response.Payload) + assert.Nil(t, ctx.Response.Error()) + + var response rpc.AppSessionsV1GetAppDefinitionResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.Nil(t, response.Definition) // Verify all mock expectations mockStore.AssertExpectations(t) diff --git a/clearnode/api/app_session_v1/get_app_sessions.go b/nitronode/api/app_session_v1/get_app_sessions.go similarity index 91% rename from clearnode/api/app_session_v1/get_app_sessions.go rename to nitronode/api/app_session_v1/get_app_sessions.go index 2c5d1c196..f942085f3 100644 --- a/clearnode/api/app_session_v1/get_app_sessions.go +++ b/nitronode/api/app_session_v1/get_app_sessions.go @@ -23,6 +23,15 @@ func (h *Handler) GetAppSessions(c *rpc.Context) { return } + if req.Participant != nil { + normalizedParticipant, err := core.NormalizeHexAddress(*req.Participant) + if err != nil { + c.Fail(rpc.Errorf("invalid participant: %v", err), "") + return + } + req.Participant = &normalizedParticipant + } + var paginationParams core.PaginationParams if req.Pagination != nil { paginationParams.Offset = req.Pagination.Offset diff --git a/clearnode/api/app_session_v1/get_app_sessions_test.go b/nitronode/api/app_session_v1/get_app_sessions_test.go similarity index 89% rename from clearnode/api/app_session_v1/get_app_sessions_test.go rename to nitronode/api/app_session_v1/get_app_sessions_test.go index 1367c81b8..4f0c06eb3 100644 --- a/clearnode/api/app_session_v1/get_app_sessions_test.go +++ b/nitronode/api/app_session_v1/get_app_sessions_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" @@ -19,7 +19,7 @@ import ( func TestGetAppSessions_SuccessWithParticipant(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -143,7 +143,7 @@ func TestGetAppSessions_SuccessWithParticipant(t *testing.T) { func TestGetAppSessions_SuccessWithAppSessionID(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -229,7 +229,7 @@ func TestGetAppSessions_SuccessWithAppSessionID(t *testing.T) { func TestGetAppSessions_MissingRequiredParams(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -274,7 +274,7 @@ func TestGetAppSessions_MissingRequiredParams(t *testing.T) { func TestGetAppSessions_WithStatusFilter(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -361,7 +361,7 @@ func TestGetAppSessions_WithStatusFilter(t *testing.T) { func TestGetAppSessions_StoreError(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -410,3 +410,34 @@ func TestGetAppSessions_StoreError(t *testing.T) { // Verify all mock expectations mockStore.AssertExpectations(t) } + +// TestGetAppSessions_NormalizesParticipant verifies the participant filter is normalized +// before being passed to the store. +func TestGetAppSessions_NormalizesParticipant(t *testing.T) { + mockStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(fn StoreTxHandler) error { return fn(mockStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + } + + canonicalParticipant := "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + mixedCaseParticipant := "0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD" + + mockStore.On("GetAppSessions", (*string)(nil), &canonicalParticipant, app.AppSessionStatusVoid, &core.PaginationParams{}). + Return([]app.AppSessionV1{}, core.PaginationMetadata{}, nil) + + reqPayload := rpc.AppSessionsV1GetAppSessionsRequest{Participant: &mixedCaseParticipant} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "app_sessions.v1.get_app_sessions", Payload: payload}, + } + + handler.GetAppSessions(ctx) + + require.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} diff --git a/nitronode/api/app_session_v1/get_last_key_states.go b/nitronode/api/app_session_v1/get_last_key_states.go new file mode 100644 index 000000000..997ebca90 --- /dev/null +++ b/nitronode/api/app_session_v1/get_last_key_states.go @@ -0,0 +1,112 @@ +package app_session_v1 + +import ( + "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. +// Mandatory pagination caps response size to prevent unbounded reads. +func (h *Handler) GetLastKeyStates(c *rpc.Context) { + ctx := c.Context + logger := log.FromContext(ctx) + + var req rpc.AppSessionsV1GetLastKeyStatesRequest + if err := c.Request.Payload.Translate(&req); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + if req.UserAddress == "" { + c.Fail(rpc.Errorf("user_address is required"), "") + return + } + + var limit, offset uint32 + if req.Pagination != nil { + // The endpoint orders rows by (created_at DESC, id ASC) for stable pagination; + // callers cannot override this, so any sort value is rejected rather than silently + // ignored. PaginationParamsV1.Sort is shared across the v1 API and other endpoints + // honor it, which is why we validate here instead of dropping the field. + if req.Pagination.Sort != nil && *req.Pagination.Sort != "" { + c.Fail(rpc.Errorf("invalid_pagination: sort is not supported by get_last_key_states"), "") + return + } + if req.Pagination.Limit != nil { + limit = *req.Pagination.Limit + } + if req.Pagination.Offset != nil { + offset = *req.Pagination.Offset + } + } + if limit == 0 || limit > rpc.GetLastKeyStatesPageLimit { + limit = rpc.GetLastKeyStatesPageLimit + } + + includeInactive := req.IncludeInactive != nil && *req.IncludeInactive + + logger.Debug("retrieving session key states", + "userAddress", req.UserAddress, + "sessionKey", req.SessionKey, + "includeInactive", includeInactive, + "limit", limit, + "offset", offset) + + var states []app.AppSessionKeyStateV1 + var totalCount uint32 + + err := h.useStoreInTx(func(tx Store) error { + var err error + states, totalCount, err = tx.GetLastAppSessionKeyStates(req.UserAddress, req.SessionKey, includeInactive, limit, offset) + return err + }) + + if err != nil { + logger.Error("failed to retrieve session key states", "error", err) + c.Fail(err, "failed to retrieve session key states") + return + } + + rpcStates := make([]rpc.AppSessionKeyStateV1, len(states)) + for i, state := range states { + rpcStates[i] = mapSessionKeyStateV1(&state) + } + + resp := rpc.AppSessionsV1GetLastKeyStatesResponse{ + States: rpcStates, + Metadata: buildPageMetadata(totalCount, limit, offset), + } + + payload, err := rpc.NewPayload(resp) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) +} + +// buildPageMetadata returns the standard pagination metadata for get_last_key_states. +// Page is 1-based and defaults to 1 (including the empty-result case, so the metadata is +// never `{page: 0, page_count: 0}`). For non-aligned offsets the page formula treats the +// offset as a row-skip count and reports the page that contains row `offset+1` — callers +// that need exact page semantics should pass offset as a multiple of limit. +func buildPageMetadata(totalCount, limit, offset uint32) rpc.PaginationMetadataV1 { + page := uint32(1) + if limit > 0 && offset >= limit { + page = (offset / limit) + 1 + } + + var pageCount uint32 + if totalCount > 0 && limit > 0 { + pageCount = (totalCount + limit - 1) / limit + } + + return rpc.PaginationMetadataV1{ + Page: page, + PerPage: limit, + TotalCount: totalCount, + PageCount: pageCount, + } +} diff --git a/nitronode/api/app_session_v1/get_last_key_states_test.go b/nitronode/api/app_session_v1/get_last_key_states_test.go new file mode 100644 index 000000000..89b758e95 --- /dev/null +++ b/nitronode/api/app_session_v1/get_last_key_states_test.go @@ -0,0 +1,168 @@ +package app_session_v1 + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func newGetLastKeyStatesHandler(store Store) *Handler { + return &Handler{ + useStoreInTx: func(fn StoreTxHandler) error { + return fn(store) + }, + } +} + +func callGetLastKeyStates(t *testing.T, h *Handler, req rpc.AppSessionsV1GetLastKeyStatesRequest) *rpc.Context { + t.Helper() + payload, err := rpc.NewPayload(req) + require.NoError(t, err) + c := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1GetLastKeyStatesMethod.String(), payload), + } + h.GetLastKeyStates(c) + return c +} + +func extractGetLastKeyStatesResponse(t *testing.T, c *rpc.Context) rpc.AppSessionsV1GetLastKeyStatesResponse { + t.Helper() + require.NotNil(t, c.Response) + require.Nil(t, c.Response.Error()) + var resp rpc.AppSessionsV1GetLastKeyStatesResponse + require.NoError(t, c.Response.Payload.Translate(&resp)) + return resp +} + +func TestGetLastKeyStates_DefaultsToPageOneOnEmptyResult(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + mockStore.On("GetLastAppSessionKeyStates", "0xuser", (*string)(nil), false, uint32(10), uint32(0)). + Return([]app.AppSessionKeyStateV1{}, 0, nil) + + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{UserAddress: "0xuser"}) + resp := extractGetLastKeyStatesResponse(t, c) + + assert.Empty(t, resp.States) + // Empty results must not produce {page: 0, page_count: 0} — page is always 1-based. + assert.Equal(t, uint32(1), resp.Metadata.Page) + assert.Equal(t, uint32(10), resp.Metadata.PerPage) + assert.Equal(t, uint32(0), resp.Metadata.TotalCount) + assert.Equal(t, uint32(0), resp.Metadata.PageCount) +} + +func TestGetLastKeyStates_PaginationMetadata_AlignedOffset(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + limit := uint32(10) + offset := uint32(10) + pagination := &rpc.PaginationParamsV1{Limit: &limit, Offset: &offset} + + mockStore.On("GetLastAppSessionKeyStates", "0xuser", (*string)(nil), false, uint32(10), uint32(10)). + Return([]app.AppSessionKeyStateV1{ + {UserAddress: "0xuser", SessionKey: "0xkey", Version: 1, ExpiresAt: time.Now().Add(time.Hour)}, + }, 25, nil) + + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + Pagination: pagination, + }) + resp := extractGetLastKeyStatesResponse(t, c) + + assert.Equal(t, uint32(2), resp.Metadata.Page) + assert.Equal(t, uint32(3), resp.Metadata.PageCount) // ceil(25/10) + assert.Equal(t, uint32(25), resp.Metadata.TotalCount) +} + +func TestGetLastKeyStates_ClampsLimitToMax(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + excessive := uint32(1000) + pagination := &rpc.PaginationParamsV1{Limit: &excessive} + + mockStore.On("GetLastAppSessionKeyStates", "0xuser", (*string)(nil), false, rpc.GetLastKeyStatesPageLimit, uint32(0)). + Return([]app.AppSessionKeyStateV1{}, 0, nil) + + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + Pagination: pagination, + }) + resp := extractGetLastKeyStatesResponse(t, c) + + assert.Equal(t, rpc.GetLastKeyStatesPageLimit, resp.Metadata.PerPage) + mockStore.AssertExpectations(t) +} + +func TestGetLastKeyStates_RejectsSortField(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + sort := "asc" + pagination := &rpc.PaginationParamsV1{Sort: &sort} + + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + Pagination: pagination, + }) + + require.NotNil(t, c.Response) + require.NotNil(t, c.Response.Error()) + assert.Contains(t, c.Response.Error().Error(), "sort is not supported") + mockStore.AssertNotCalled(t, "GetLastAppSessionKeyStates", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestGetLastKeyStates_RequiresUserAddress(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{}) + + require.NotNil(t, c.Response) + require.NotNil(t, c.Response.Error()) + assert.Contains(t, c.Response.Error().Error(), "user_address is required") +} + +func TestGetLastKeyStates_IncludeInactiveTruePlumbsToStore(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + mockStore.On("GetLastAppSessionKeyStates", "0xuser", (*string)(nil), true, uint32(10), uint32(0)). + Return([]app.AppSessionKeyStateV1{}, 0, nil) + + includeInactive := true + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + IncludeInactive: &includeInactive, + }) + _ = extractGetLastKeyStatesResponse(t, c) + + mockStore.AssertExpectations(t) +} + +func TestGetLastKeyStates_IncludeInactiveFalsePlumbsToStore(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + mockStore.On("GetLastAppSessionKeyStates", "0xuser", (*string)(nil), false, uint32(10), uint32(0)). + Return([]app.AppSessionKeyStateV1{}, 0, nil) + + includeInactive := false + c := callGetLastKeyStates(t, h, rpc.AppSessionsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + IncludeInactive: &includeInactive, + }) + _ = extractGetLastKeyStatesResponse(t, c) + + mockStore.AssertExpectations(t) +} diff --git a/clearnode/api/app_session_v1/handler.go b/nitronode/api/app_session_v1/handler.go similarity index 52% rename from clearnode/api/app_session_v1/handler.go rename to nitronode/api/app_session_v1/handler.go index bfac5079b..ac30997c4 100644 --- a/clearnode/api/app_session_v1/handler.go +++ b/nitronode/api/app_session_v1/handler.go @@ -7,28 +7,38 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/shopspring/decimal" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/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" ) +// Handlers in this package stamp DB writes with the session's stored +// ApplicationID (persisted at app_session creation) rather than the +// connection-scoped tag returned by rpc.GetApplicationID. This keeps app +// session state attributable to the owning application even when a caller +// connects without an app_id query parameter or with a different one — +// e.g., a co-participant, a relay, or a re-connection. Channel-level +// handlers (see nitronode/api/channel_v1) have no session to anchor to and +// therefore use the connection tag. + // 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 - nodeAddress string // Node's wallet address - metrics metrics.RuntimeMetricExporter - maxParticipants int - maxSessionData int - maxSessionKeyIDs int - maxSignedUpdates int + useStoreInTx StoreTxProvider + assetStore AssetStore + actionGateway ActionGateway + signer *core.ChannelDefaultSigner + stateAdvancer core.StateAdvancer + statePacker core.StatePacker + nodeAddress string // Node's wallet address + appRegistryEnabled bool + metrics metrics.RuntimeMetricExporter + maxParticipants int + maxSessionData int + maxSessionKeyIDs int + maxSignedUpdates int + maxSessionKeysPerUser int } // NewHandler creates a new Handler instance with the provided dependencies. @@ -36,26 +46,30 @@ func NewHandler( useStoreInTx StoreTxProvider, assetStore AssetStore, actionGateway ActionGateway, - signer sign.Signer, + signer *core.ChannelDefaultSigner, stateAdvancer core.StateAdvancer, statePacker core.StatePacker, nodeAddress string, + appRegistryEnabled bool, m metrics.RuntimeMetricExporter, maxParticipants, maxSessionData, maxSessionKeyIDs, maxSignedUpdates int, + maxSessionKeysPerUser int, ) *Handler { return &Handler{ - useStoreInTx: useStoreInTx, - assetStore: assetStore, - actionGateway: actionGateway, - signer: signer, - stateAdvancer: stateAdvancer, - statePacker: statePacker, - nodeAddress: nodeAddress, - metrics: m, - maxParticipants: maxParticipants, - maxSessionData: maxSessionData, - maxSessionKeyIDs: maxSessionKeyIDs, - maxSignedUpdates: maxSignedUpdates, + useStoreInTx: useStoreInTx, + assetStore: assetStore, + actionGateway: actionGateway, + signer: signer, + stateAdvancer: stateAdvancer, + statePacker: statePacker, + nodeAddress: nodeAddress, + appRegistryEnabled: appRegistryEnabled, + metrics: m, + maxParticipants: maxParticipants, + maxSessionData: maxSessionData, + maxSessionKeyIDs: maxSessionKeyIDs, + maxSignedUpdates: maxSignedUpdates, + maxSessionKeysPerUser: maxSessionKeysPerUser, } } @@ -66,16 +80,20 @@ func (h *Handler) verifyQuorum(tx Store, appSessionId, applicationID string, par appSessionSignerValidator := app.NewAppSessionKeySigValidatorV1( func(sessionKeyAddr string) (string, error) { - return tx.GetAppSessionKeyOwner(sessionKeyAddr, appSessionId) + return tx.GetAppSessionKeyOwner(sessionKeyAddr, appSessionId, applicationID) }, ) - for _, sigHex := range signatures { + for i, sigHex := range signatures { sigBytes, err := hexutil.Decode(sigHex) if err != nil { return rpc.Errorf("failed to decode signature: %v", err) } + if len(sigBytes) == 0 { + return rpc.Errorf("empty signature after decode at index %d", i) + } + sigType := app.AppSessionSignerTypeV1(sigBytes[0]) userWallet, err := appSessionSignerValidator.Recover(data, sigBytes) if err != nil { @@ -108,7 +126,7 @@ func (h *Handler) verifyQuorum(tx Store, appSessionId, applicationID string, par // issueReleaseReceiverState creates a new channel state for a participant receiving funds from app session. // This follows the same pattern as issueTransferReceiverState in channel_v1 for transfer_receive transitions. -func (h *Handler) issueReleaseReceiverState(ctx context.Context, tx Store, receiverWallet, asset, appSessionID string, amount decimal.Decimal) error { +func (h *Handler) issueReleaseReceiverState(ctx context.Context, tx Store, receiverWallet, asset, appSessionID string, amount decimal.Decimal, applicationID string) error { logger := log.FromContext(ctx) // Lock the receiver's state to prevent concurrent modifications @@ -141,42 +159,52 @@ func (h *Handler) issueReleaseReceiverState(ctx context.Context, tx Store, recei return rpc.Errorf("failed to apply release transition: %v", err) } - // Check if we need to sign the state (skip signing if last signed state was a lock) - lastSignedState, err := tx.GetLastUserState(receiverWallet, asset, true) - if err != nil { - return rpc.Errorf("failed to get last signed state: %v", err) - } - - // TODO: move to DB query - shouldSign := true - if lastSignedState != nil { - lastStateTransition := lastSignedState.Transition - - if lastStateTransition.Type == core.TransitionTypeMutualLock || - lastStateTransition.Type == core.TransitionTypeEscrowLock { - shouldSign = false - } - + if err := tx.EnsureNoOngoingEscrowOperation(receiverWallet, asset); err != nil { + return rpc.Errorf("cannot issue release receiver state: %v", err) } - if newState.HomeChannelID != nil && shouldSign { - // Pack and sign the state - packedState, err := h.statePacker.PackState(*newState) + if newState.HomeChannelID != nil { + // Same rule as channel_v1.issueTransferReceiverState: sign only when the + // receiver's home channel is Open. CheckActiveChannel returns nil status for + // Challenged / Closing / Closed channels, in which case the state is stored + // unsigned so the challenge-rescue squash at close (Challenged path) can pick + // it up, and so terminal-status channels never receive a node-signed credit + // that won't settle. + // + // MF3-I01 (release path): same reasoning as channel_v1. The listener + // ordering & idempotency invariant (pkg/blockchain/evm/listener.go, + // processEvents doc) guarantees HandleHomeChannelChallenged precedes + // HandleHomeChannelClosed for any Path-1 (challenge-timeout) close, so + // an unsigned release credit either lands before the close handler runs + // (and is squashed into the rescue sum) or after it (and reads the + // rescue row as currentState, with HomeChannelID=nil). The wedge state + // where currentState transitively points at a Closed channel is not + // reachable through the supported event-ingestion path. + _, channelStatus, err := tx.CheckActiveChannel(receiverWallet, asset) if err != nil { - return rpc.Errorf("failed to pack receiver state: %v", err) + return rpc.Errorf("failed to check receiver active channel: %v", err) } - - nodeSig, err := h.signer.Sign(packedState) - if err != nil { - return rpc.Errorf("failed to sign receiver state: %v", err) + if channelStatus != nil && *channelStatus == core.ChannelStatusOpen { + packedState, err := h.statePacker.PackState(*newState) + if err != nil { + return rpc.Errorf("failed to pack receiver state: %v", err) + } + + nodeSig, err := h.signer.Sign(packedState) + if err != nil { + return rpc.Errorf("failed to sign receiver state: %v", err) + } + + nodeSigStr := nodeSig.String() + newState.NodeSig = &nodeSigStr + } else { + logger.Info("skipping node signature on receiver state for non-open home channel", + "homeChannelID", *newState.HomeChannelID) } - - nodeSigStr := nodeSig.String() - newState.NodeSig = &nodeSigStr } // Store the new state - if err := tx.StoreUserState(*newState); err != nil { + if err := tx.StoreUserState(*newState, applicationID); err != nil { return rpc.Errorf("failed to store receiver state: %v", err) } @@ -185,7 +213,7 @@ func (h *Handler) issueReleaseReceiverState(ctx context.Context, tx Store, recei return rpc.Errorf("failed to create transaction: %v", err) } - if err := tx.RecordTransaction(*transaction); err != nil { + if err := tx.RecordTransaction(*transaction, applicationID); err != nil { return rpc.Errorf("failed to record transaction: %v", err) } logger.Info("recorded transaction", diff --git a/clearnode/api/app_session_v1/interface.go b/nitronode/api/app_session_v1/interface.go similarity index 60% rename from clearnode/api/app_session_v1/interface.go rename to nitronode/api/app_session_v1/interface.go index 5c13464b9..6a1185aae 100644 --- a/clearnode/api/app_session_v1/interface.go +++ b/nitronode/api/app_session_v1/interface.go @@ -1,7 +1,10 @@ package app_session_v1 import ( - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "time" + + "github.com/layer-3/nitrolite/nitronode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" @@ -23,7 +26,9 @@ type Store interface { // Ledger operations RecordLedgerEntry(userWallet, accountID, asset string, amount decimal.Decimal) error - RecordTransaction(tx core.Transaction) error + // RecordTransaction records a transaction. applicationID is the client-declared + // origin tag (rpc.ApplicationIDQueryParam); empty string is persisted as NULL. + RecordTransaction(tx core.Transaction, applicationID string) error // Channel state operations @@ -31,18 +36,35 @@ type Store interface { // Returns the current balance. LockUserState(wallet, asset string) (decimal.Decimal, error) - // CheckOpenChannel verifies if a user has an active channel for the given asset - // and returns the approved signature validators if such a channel exists. - CheckOpenChannel(wallet, asset string) (string, bool, error) + // CheckActiveChannel verifies if a user has an active home channel for the given asset + // and returns its approved signature validators and current status. A nil status means + // no active channel exists. "Active" includes Void (DB-only, awaiting onchain confirmation) + // and Open (materialized onchain); callers needing onchain materialization must additionally + // require Status == core.ChannelStatusOpen. + CheckActiveChannel(wallet, asset string) (string, *core.ChannelStatus, error) + GetLastUserState(wallet, asset string, signed bool) (*core.State, error) - StoreUserState(state core.State) error + // StoreUserState persists a user state. applicationID is the client-declared + // origin tag (rpc.ApplicationIDQueryParam); empty string is persisted as NULL. + StoreUserState(state core.State, applicationID string) error EnsureNoOngoingStateTransitions(wallet, asset string) error + // EnsureNoOngoingEscrowOperation validates that the user has no in-flight escrow + // operation (escrow_lock, mutual_lock, or unfinalized escrow_deposit/escrow_withdraw) + // that would prevent issuing a receiver-side state. + EnsureNoOngoingEscrowOperation(wallet, asset string) error + // App Session key state operations + LockSessionKeyState(userAddress, sessionKey string, kind database.SessionKeyKind) (latestVersion uint64, latestExpiresAt time.Time, err error) + CountSessionKeysForUser(userAddress string) (uint32, error) StoreAppSessionKeyState(state app.AppSessionKeyStateV1) error GetLastAppSessionKeyVersion(wallet, sessionKey string) (uint64, error) - GetLastAppSessionKeyStates(wallet string, sessionKey *string) ([]app.AppSessionKeyStateV1, error) - GetAppSessionKeyOwner(sessionKey, appSessionId string) (string, error) + // GetLastAppSessionKeyStates retrieves the latest app session key states for a user, + // optionally filtered by session key. When includeInactive is false, only non-expired + // latest states are returned; when true, all latest states are returned regardless of + // expiry. Results are paginated. + GetLastAppSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]app.AppSessionKeyStateV1, uint32, error) + GetAppSessionKeyOwner(sessionKey, appSessionId, applicationId string) (string, error) // Channel Session key state operations ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) diff --git a/clearnode/api/app_session_v1/rebalance_app_sessions.go b/nitronode/api/app_session_v1/rebalance_app_sessions.go similarity index 88% rename from clearnode/api/app_session_v1/rebalance_app_sessions.go rename to nitronode/api/app_session_v1/rebalance_app_sessions.go index f066c6cf1..9f1b45ed7 100644 --- a/clearnode/api/app_session_v1/rebalance_app_sessions.go +++ b/nitronode/api/app_session_v1/rebalance_app_sessions.go @@ -76,6 +76,8 @@ func (h *Handler) RebalanceAppSessions(c *rpc.Context) { var batchID string err := h.useStoreInTx(func(tx Store) error { + // applicationID is the shared application of all rebalanced sessions; enforced below. + var applicationID string // Generate deterministic batch ID from session IDs and versions sessionVersions := make([]app.AppSessionVersionV1, len(updates)) for i, u := range updates { @@ -95,7 +97,7 @@ func (h *Handler) RebalanceAppSessions(c *rpc.Context) { assetTotalDiff := make(map[string]decimal.Decimal) // asset -> total change // Validate and process each session - for _, update := range updates { + for i, update := range updates { appSession, err := tx.GetAppSession(update.AppStateUpdate.AppSessionID) if err != nil { return rpc.Errorf("failed to get app session %s: %v", update.AppStateUpdate.AppSessionID, err) @@ -103,17 +105,28 @@ 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) + + // All rebalanced sessions must belong to the same application. + // Quoting keeps legacy (untagged, "") sessions visually + // distinguishable from tagged ones in the error message. + if i == 0 { + applicationID = appSession.ApplicationID + } else if appSession.ApplicationID != applicationID { + return rpc.Errorf("cannot rebalance app sessions from different applications: '%s' vs '%s'", applicationID, appSession.ApplicationID) } + if h.appRegistryEnabled { + 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) + 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)) @@ -160,6 +173,14 @@ func (h *Handler) RebalanceAppSessions(c *rpc.Context) { alloc.Amount, alloc.Asset, update.AppStateUpdate.AppSessionID) } + // Reject duplicate (participant, asset) entries + if newAllocations[alloc.Participant] != nil { + if _, exists := newAllocations[alloc.Participant][alloc.Asset]; exists { + return rpc.Errorf("duplicate allocation for participant %s, asset %s in session %s", + alloc.Participant, alloc.Asset, update.AppStateUpdate.AppSessionID) + } + } + if newAllocations[alloc.Participant] == nil { newAllocations[alloc.Participant] = make(map[string]decimal.Decimal) } @@ -292,7 +313,7 @@ func (h *Handler) RebalanceAppSessions(c *rpc.Context) { amount, ) - if err := tx.RecordTransaction(*transaction); err != nil { + if err := tx.RecordTransaction(*transaction, applicationID); err != nil { return rpc.Errorf("failed to record transaction for session %s: %v", sessionID, err) } diff --git a/clearnode/api/app_session_v1/rebalance_app_sessions_test.go b/nitronode/api/app_session_v1/rebalance_app_sessions_test.go similarity index 69% rename from clearnode/api/app_session_v1/rebalance_app_sessions_test.go rename to nitronode/api/app_session_v1/rebalance_app_sessions_test.go index aef9cb2e9..264bcb2ba 100644 --- a/clearnode/api/app_session_v1/rebalance_app_sessions_test.go +++ b/nitronode/api/app_session_v1/rebalance_app_sessions_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" @@ -48,8 +48,9 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create test wallets with real keys @@ -178,7 +179,7 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { mockStore.On("RecordLedgerEntry", wallet2.Address, sessionID2, "USDC", decimal.NewFromInt(100)).Return(nil) mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { return tx.TxType == core.TransactionTypeRebalance && tx.Asset == "USDC" - })).Return(nil).Twice() + }), mock.Anything).Return(nil).Twice() // Create RPC context payload, err := rpc.NewPayload(reqPayload) @@ -219,8 +220,9 @@ func TestRebalanceAppSessions_Success_MultiAsset(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create test wallets with real keys @@ -344,7 +346,7 @@ func TestRebalanceAppSessions_Success_MultiAsset(t *testing.T) { mockStore.On("RecordLedgerEntry", wallet1.Address, sessionID1, "ETH", decimal.RequireFromString("0.5")).Return(nil) mockStore.On("RecordLedgerEntry", wallet2.Address, sessionID2, "USDC", decimal.NewFromInt(100)).Return(nil) mockStore.On("RecordLedgerEntry", wallet2.Address, sessionID2, "ETH", decimal.RequireFromString("-0.5")).Return(nil) - mockStore.On("RecordTransaction", mock.Anything).Return(nil).Times(4) // 2 assets x 2 sessions + mockStore.On("RecordTransaction", mock.Anything, mock.Anything).Return(nil).Times(4) // 2 assets x 2 sessions // Create RPC context payload, err := rpc.NewPayload(reqPayload) @@ -379,8 +381,9 @@ func TestRebalanceAppSessions_Error_InsufficientSessions(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -437,8 +440,9 @@ func TestRebalanceAppSessions_Error_InvalidIntent(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -511,8 +515,9 @@ func TestRebalanceAppSessions_Error_DuplicateSession(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -587,8 +592,9 @@ func TestRebalanceAppSessions_Error_ConservationViolation(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) // Create test wallets with real keys @@ -733,8 +739,9 @@ func TestRebalanceAppSessions_Error_SessionNotFound(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -814,8 +821,9 @@ func TestRebalanceAppSessions_Error_ClosedSession(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -909,8 +917,9 @@ func TestRebalanceAppSessions_Error_InvalidVersion(t *testing.T) { nil, nil, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) wallet1 := NewTestAppSessionWallet(t) @@ -987,3 +996,363 @@ func TestRebalanceAppSessions_Error_InvalidVersion(t *testing.T) { assertError(t, ctx, "invalid version") mockStore.AssertExpectations(t) } + +// TestRebalanceAppSessions_AppRegistryDisabled verifies that when appRegistryEnabled=false, +// app lookup and AllowAction are skipped but rebalance still succeeds. +func TestRebalanceAppSessions_AppRegistryDisabled(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler( + storeTxProvider, + nil, + &MockActionGateway{}, + nil, + nil, + nil, + "0xNode", + false, // appRegistryEnabled=false + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + wallet1 := NewTestAppSessionWallet(t) + wallet2 := NewTestAppSessionWallet(t) + + sessionID1 := "0x1111111111111111111111111111111111111111111111111111111111111111" + sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" + + session1 := &app.AppSessionV1{ + SessionID: sessionID1, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{{WalletAddress: wallet1.Address, SignatureWeight: 10}}, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 5, + } + + session2 := &app.AppSessionV1{ + SessionID: sessionID2, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{{WalletAddress: wallet2.Address, SignatureWeight: 10}}, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 3, + } + + currentAllocations1 := map[string]map[string]decimal.Decimal{ + wallet1.Address: {"USDC": decimal.NewFromInt(200)}, + } + currentAllocations2 := map[string]map[string]decimal.Decimal{ + wallet2.Address: {"USDC": decimal.NewFromInt(50)}, + } + + appStateUpdate1 := app.AppStateUpdateV1{ + AppSessionID: sessionID1, + Intent: app.AppStateUpdateIntentRebalance, + Version: 6, + Allocations: []app.AppAllocationV1{{Participant: wallet1.Address, Asset: "USDC", Amount: decimal.NewFromInt(100)}}, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdate1) + + appStateUpdate2 := app.AppStateUpdateV1{ + AppSessionID: sessionID2, + Intent: app.AppStateUpdateIntentRebalance, + Version: 4, + Allocations: []app.AppAllocationV1{{Participant: wallet2.Address, Asset: "USDC", Amount: decimal.NewFromInt(150)}}, + } + sig2 := wallet2.SignAppStateUpdate(t, appStateUpdate2) + + reqPayload := rpc.AppSessionsV1RebalanceAppSessionsRequest{ + SignedUpdates: []rpc.SignedAppStateUpdateV1{ + { + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: sessionID1, + Intent: app.AppStateUpdateIntentRebalance, + Version: "6", + Allocations: []rpc.AppAllocationV1{{Participant: wallet1.Address, Asset: "USDC", Amount: "100"}}, + }, + QuorumSigs: []string{sig1}, + }, + { + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: sessionID2, + Intent: app.AppStateUpdateIntentRebalance, + Version: "4", + Allocations: []rpc.AppAllocationV1{{Participant: wallet2.Address, Asset: "USDC", Amount: "150"}}, + }, + QuorumSigs: []string{sig2}, + }, + }, + } + + // NO GetApp mock — it should not be called + mockStore.On("GetAppSession", sessionID1).Return(session1, nil) + mockStore.On("GetParticipantAllocations", sessionID1).Return(currentAllocations1, nil) + mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { + return session.SessionID == sessionID1 && session.Version == 6 + })).Return(nil).Once() + + mockStore.On("GetAppSession", sessionID2).Return(session2, nil) + mockStore.On("GetParticipantAllocations", sessionID2).Return(currentAllocations2, nil) + mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { + return session.SessionID == sessionID2 && session.Version == 4 + })).Return(nil).Once() + + mockStore.On("RecordLedgerEntry", wallet1.Address, sessionID1, "USDC", decimal.NewFromInt(-100)).Return(nil) + mockStore.On("RecordLedgerEntry", wallet2.Address, sessionID2, "USDC", decimal.NewFromInt(100)).Return(nil) + mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + return tx.TxType == core.TransactionTypeRebalance && tx.Asset == "USDC" + }), mock.Anything).Return(nil).Twice() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "app_sessions.v1.rebalance_app_sessions", payload), + } + + handler.RebalanceAppSessions(ctx) + + assertSuccess(t, ctx) + + // Strict: GetApp must NOT have been called + mockStore.AssertNotCalled(t, "GetApp", mock.Anything) + mockStore.AssertExpectations(t) +} + +func TestRebalanceAppSessions_Error_DuplicateAllocation(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler( + storeTxProvider, + nil, + &MockActionGateway{}, + nil, + nil, + nil, + "0xNode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + wallet1 := NewTestAppSessionWallet(t) + wallet2 := NewTestAppSessionWallet(t) + + sessionID1 := "0x1111111111111111111111111111111111111111111111111111111111111111" + sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" + + session1 := &app.AppSessionV1{ + SessionID: sessionID1, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: wallet1.Address, SignatureWeight: 10}, + }, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + session2 := &app.AppSessionV1{ + SessionID: sessionID2, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: wallet2.Address, SignatureWeight: 10}, + }, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + currentAllocations1 := map[string]map[string]decimal.Decimal{ + wallet1.Address: {"USDC": decimal.NewFromInt(200)}, + } + + currentAllocations2 := map[string]map[string]decimal.Decimal{ + wallet2.Address: {"USDC": decimal.NewFromInt(50)}, + } + + // Session 1 has duplicate (wallet1, USDC) allocations + appStateUpdate1 := app.AppStateUpdateV1{ + AppSessionID: sessionID1, + Intent: app.AppStateUpdateIntentRebalance, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: wallet1.Address, Asset: "USDC", Amount: decimal.NewFromInt(100)}, + {Participant: wallet1.Address, Asset: "USDC", Amount: decimal.NewFromInt(50)}, // duplicate + }, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdate1) + + appStateUpdate2 := app.AppStateUpdateV1{ + AppSessionID: sessionID2, + Intent: app.AppStateUpdateIntentRebalance, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: wallet2.Address, Asset: "USDC", Amount: decimal.NewFromInt(100)}, + }, + } + sig2 := wallet2.SignAppStateUpdate(t, appStateUpdate2) + + reqPayload := rpc.AppSessionsV1RebalanceAppSessionsRequest{ + SignedUpdates: []rpc.SignedAppStateUpdateV1{ + { + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: sessionID1, + Intent: app.AppStateUpdateIntentRebalance, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: wallet1.Address, Asset: "USDC", Amount: "100"}, + {Participant: wallet1.Address, Asset: "USDC", Amount: "50"}, // duplicate + }, + }, + QuorumSigs: []string{sig1}, + }, + { + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: sessionID2, + Intent: app.AppStateUpdateIntentRebalance, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: wallet2.Address, Asset: "USDC", Amount: "100"}, + }, + }, + QuorumSigs: []string{sig2}, + }, + }, + } + + 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) + // Session 2 mocks in case session 1 processing doesn't fail first + mockStore.On("GetAppSession", sessionID2).Return(session2, nil).Maybe() + mockStore.On("GetParticipantAllocations", sessionID2).Return(currentAllocations2, nil).Maybe() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "app_sessions.v1.rebalance_app_sessions", payload), + } + + handler.RebalanceAppSessions(ctx) + + assertError(t, ctx, "duplicate allocation") + + mockStore.AssertNotCalled(t, "RecordLedgerEntry", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestRebalanceAppSessions_Error_DifferentApplications(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler( + storeTxProvider, + nil, + &MockActionGateway{}, + nil, + nil, + nil, + "0xNode", + false, // registry disabled — simpler: skip GetApp path + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + wallet1 := NewTestAppSessionWallet(t) + wallet2 := NewTestAppSessionWallet(t) + + sessionID1 := "0x1111111111111111111111111111111111111111111111111111111111111111" + sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" + + session1 := &app.AppSessionV1{ + SessionID: sessionID1, + ApplicationID: "app-one", + Participants: []app.AppParticipantV1{{WalletAddress: wallet1.Address, SignatureWeight: 10}}, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 5, + } + session2 := &app.AppSessionV1{ + SessionID: sessionID2, + ApplicationID: "app-two", // Different application + Participants: []app.AppParticipantV1{{WalletAddress: wallet2.Address, SignatureWeight: 10}}, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 3, + } + + appStateUpdate1 := app.AppStateUpdateV1{ + AppSessionID: sessionID1, + Intent: app.AppStateUpdateIntentRebalance, + Version: 6, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdate1) + + appStateUpdate2 := app.AppStateUpdateV1{ + AppSessionID: sessionID2, + Intent: app.AppStateUpdateIntentRebalance, + Version: 4, + } + sig2 := wallet2.SignAppStateUpdate(t, appStateUpdate2) + + reqPayload := rpc.AppSessionsV1RebalanceAppSessionsRequest{ + SignedUpdates: []rpc.SignedAppStateUpdateV1{ + { + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: sessionID1, + Intent: app.AppStateUpdateIntentRebalance, + Version: "6", + }, + QuorumSigs: []string{sig1}, + }, + { + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: sessionID2, + Intent: app.AppStateUpdateIntentRebalance, + Version: "4", + }, + QuorumSigs: []string{sig2}, + }, + }, + } + + mockStore.On("GetAppSession", sessionID1).Return(session1, nil) + mockStore.On("GetAppSession", sessionID2).Return(session2, nil) + // Session 1 is fully processed (tx rolls back on failure); session 2 trips the cross-app check. + emptyAllocations := map[string]map[string]decimal.Decimal{} + mockStore.On("GetParticipantAllocations", sessionID1).Return(emptyAllocations, nil).Maybe() + mockStore.On("UpdateAppSession", mock.Anything).Return(nil).Maybe() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "app_sessions.v1.rebalance_app_sessions", payload), + } + + handler.RebalanceAppSessions(ctx) + + assertError(t, ctx, "cannot rebalance app sessions from different applications") + + // Ledger/transaction writes happen only after all per-session validation completes, + // so the cross-app failure on session 2 must prevent them entirely. + mockStore.AssertNotCalled(t, "RecordLedgerEntry", mock.Anything, mock.Anything, mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) +} diff --git a/clearnode/api/app_session_v1/submit_app_state.go b/nitronode/api/app_session_v1/submit_app_state.go similarity index 84% rename from clearnode/api/app_session_v1/submit_app_state.go rename to nitronode/api/app_session_v1/submit_app_state.go index e5c1290c5..cdf4652c1 100644 --- a/clearnode/api/app_session_v1/submit_app_state.go +++ b/nitronode/api/app_session_v1/submit_app_state.go @@ -2,6 +2,7 @@ package app_session_v1 import ( "context" + "sort" "time" "github.com/layer-3/nitrolite/pkg/app" @@ -65,16 +66,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 h.appRegistryEnabled { + 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) { @@ -115,13 +118,13 @@ func (h *Handler) SubmitAppState(c *rpc.Context) { case app.AppStateUpdateIntentWithdraw: // For withdraw intent, validate and record ledger changes - if err := h.handleWithdrawIntent(ctx, tx, appStateUpd, currentAllocations, participantWeights); err != nil { + if err := h.handleWithdrawIntent(ctx, tx, appStateUpd, currentAllocations, participantWeights, appSession.ApplicationID); err != nil { return err } case app.AppStateUpdateIntentClose: // For close intent, validate final allocations and mark session as closed - if err := h.handleCloseIntent(ctx, tx, appStateUpd, currentAllocations, participantWeights); err != nil { + if err := h.handleCloseIntent(ctx, tx, appStateUpd, currentAllocations, participantWeights, appSession.ApplicationID); err != nil { return err } appSession.Status = app.AppSessionStatusClosed @@ -203,6 +206,13 @@ func (h *Handler) handleOperateIntent( return rpc.Errorf("invalid amount for allocation with asset %s and participant %s: %w", alloc.Asset, alloc.Participant, err) } + // Reject duplicate (participant, asset) entries + if incomingAllocations[alloc.Participant] != nil { + if _, exists := incomingAllocations[alloc.Participant][alloc.Asset]; exists { + return rpc.Errorf("duplicate allocation for participant %s, asset %s", alloc.Participant, alloc.Asset) + } + } + // Sum up allocations per asset if existing, ok := allocationSum[alloc.Asset]; ok { allocationSum[alloc.Asset] = existing.Add(alloc.Amount) @@ -301,6 +311,7 @@ func (h *Handler) handleWithdrawIntent( appStateUpd app.AppStateUpdateV1, currentAllocations map[string]map[string]decimal.Decimal, participantWeights map[string]uint8, + applicationID string, ) error { // Build incoming allocations map for validation incomingAllocations := make(map[string]map[string]decimal.Decimal) @@ -315,6 +326,13 @@ func (h *Handler) handleWithdrawIntent( return rpc.Errorf("negative allocation: %s for asset %s", alloc.Amount, alloc.Asset) } + // Reject duplicate (participant, asset) entries + if incomingAllocations[alloc.Participant] != nil { + if _, exists := incomingAllocations[alloc.Participant][alloc.Asset]; exists { + return rpc.Errorf("duplicate allocation for participant %s, asset %s", alloc.Participant, alloc.Asset) + } + } + // Check for new allocations (reject if current is zero but incoming is non-zero) if !alloc.Amount.IsZero() { currentAmount := decimal.Zero @@ -335,9 +353,25 @@ func (h *Handler) handleWithdrawIntent( incomingAllocations[alloc.Participant][alloc.Asset] = alloc.Amount } + // Sort participant keys to ensure deterministic lock ordering and prevent deadlocks + participants := make([]string, 0, len(currentAllocations)) + for participant := range currentAllocations { + participants = append(participants, participant) + } + sort.Strings(participants) + // Verify all current allocations are present and process withdrawals - for participant, assets := range currentAllocations { - for asset, currentAmount := range assets { + for _, participant := range participants { + assets := currentAllocations[participant] + + assetKeys := make([]string, 0, len(assets)) + for asset := range assets { + assetKeys = append(assetKeys, asset) + } + sort.Strings(assetKeys) + + for _, asset := range assetKeys { + currentAmount := assets[asset] if currentAmount.IsZero() { continue } @@ -372,7 +406,7 @@ func (h *Handler) handleWithdrawIntent( } // Issue new channel state for participant receiving withdrawn funds - if err := h.issueReleaseReceiverState(ctx, tx, participant, asset, appStateUpd.AppSessionID, withdrawAmount); err != nil { + if err := h.issueReleaseReceiverState(ctx, tx, participant, asset, appStateUpd.AppSessionID, withdrawAmount, applicationID); err != nil { return rpc.Errorf("failed to issue release state for participant %s: %v", participant, err) } } @@ -390,6 +424,7 @@ func (h *Handler) handleCloseIntent( appStateUpd app.AppStateUpdateV1, currentAllocations map[string]map[string]decimal.Decimal, participantWeights map[string]uint8, + applicationID string, ) error { // Build a map of incoming allocations for easy lookup incomingAllocations := make(map[string]map[string]decimal.Decimal) @@ -403,6 +438,13 @@ func (h *Handler) handleCloseIntent( return rpc.Errorf("negative allocation: %s for asset %s", alloc.Amount, alloc.Asset) } + // Reject duplicate (participant, asset) entries + if incomingAllocations[alloc.Participant] != nil { + if _, exists := incomingAllocations[alloc.Participant][alloc.Asset]; exists { + return rpc.Errorf("duplicate allocation for participant %s, asset %s", alloc.Participant, alloc.Asset) + } + } + if incomingAllocations[alloc.Participant] == nil { incomingAllocations[alloc.Participant] = make(map[string]decimal.Decimal) } @@ -447,9 +489,25 @@ func (h *Handler) handleCloseIntent( } } + // Sort participant keys to ensure deterministic lock ordering and prevent deadlocks + participants := make([]string, 0, len(currentAllocations)) + for participant := range currentAllocations { + participants = append(participants, participant) + } + sort.Strings(participants) + // Iterate over current allocations and release each non-zero amount - for participant, assets := range currentAllocations { - for asset, amount := range assets { + for _, participant := range participants { + assets := currentAllocations[participant] + + assetKeys := make([]string, 0, len(assets)) + for asset := range assets { + assetKeys = append(assetKeys, asset) + } + sort.Strings(assetKeys) + + for _, asset := range assetKeys { + amount := assets[asset] if amount.IsZero() { continue } @@ -460,7 +518,7 @@ func (h *Handler) handleCloseIntent( } // Issue new channel state for participant receiving funds back - if err := h.issueReleaseReceiverState(ctx, tx, participant, asset, appStateUpd.AppSessionID, amount); err != nil { + if err := h.issueReleaseReceiverState(ctx, tx, participant, asset, appStateUpd.AppSessionID, amount, applicationID); err != nil { return rpc.Errorf("failed to issue release state for participant %s: %v", participant, err) } } diff --git a/clearnode/api/app_session_v1/submit_app_state_test.go b/nitronode/api/app_session_v1/submit_app_state_test.go similarity index 62% rename from clearnode/api/app_session_v1/submit_app_state_test.go rename to nitronode/api/app_session_v1/submit_app_state_test.go index 3638e600b..04e2fed90 100644 --- a/clearnode/api/app_session_v1/submit_app_state_test.go +++ b/nitronode/api/app_session_v1/submit_app_state_test.go @@ -3,9 +3,11 @@ package app_session_v1 import ( "context" "errors" + "fmt" + "strings" "testing" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" @@ -22,7 +24,7 @@ func TestSubmitAppState_OperateIntent_NoRedistribution_Success(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -34,8 +36,9 @@ func TestSubmitAppState_OperateIntent_NoRedistribution_Success(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -137,7 +140,7 @@ func TestSubmitAppState_OperateIntent_WithRedistribution_Success(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -149,8 +152,9 @@ func TestSubmitAppState_OperateIntent_WithRedistribution_Success(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -253,7 +257,8 @@ func TestSubmitAppState_OperateIntent_WithRedistribution_Success(t *testing.T) { func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() + nodeAddress := strings.ToLower(mockSigner.PublicKey().Address().String()) storeTxProvider := func(fn StoreTxHandler) error { return fn(mockStore) @@ -269,9 +274,10 @@ func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, - "0xNode", + nodeAddress, + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -331,12 +337,30 @@ func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { mockStore.On("RecordLedgerEntry", participant1, appSessionID, "USDC", decimal.NewFromInt(-40)).Return(nil) // Mock expectations for channel state issuance (issueReleaseReceiverState) + homeChannelID := "0xHomeChannel" + existingUserState := core.State{ + Asset: "USDC", + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(200), + UserNetFlow: decimal.NewFromInt(200), + }, + } mockStore.On("LockUserState", participant1, "USDC").Return(decimal.Zero, nil) - mockStore.On("GetLastUserState", participant1, "USDC", false).Return(nil, nil) - mockStore.On("GetLastUserState", participant1, "USDC", true).Return(nil, nil) + mockStore.On("GetLastUserState", participant1, "USDC", false).Return(existingUserState, nil) + mockStore.On("EnsureNoOngoingEscrowOperation", participant1, "USDC").Return(nil) + mockStore.On("CheckActiveChannel", participant1, "USDC").Return("0x03", core.ChannelStatusOpen, nil) mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) - mockStore.On("RecordTransaction", mock.Anything).Return(nil) - mockStore.On("StoreUserState", mock.Anything).Return(nil) + mockStore.On("RecordTransaction", mock.Anything, mock.Anything).Return(nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), mock.Anything).Return(nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { return session.Version == 2 && session.Status == app.AppSessionStatusOpen @@ -361,13 +385,147 @@ func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { } assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) + // Verify node signature on stored channel state + require.NotNil(t, capturedState.NodeSig, "Node signature should be present on stored state") + VerifyNodeSignature(t, nodeAddress, []byte("packed"), *capturedState.NodeSig) + mockStore.AssertExpectations(t) } -func TestSubmitAppState_CloseIntent_Success(t *testing.T) { +func TestSubmitAppState_WithdrawIntent_ReceiverHomeChannelChallenged_NoNodeSig(t *testing.T) { + // When the receiver's home channel is Challenged, the release receiver state must be + // persisted without a node signature. Mirrors the channel_v1 protection: otherwise an + // attacker holding an expired or out-of-scope session key could checkpoint a + // node-signed release-credit state and reset the dispute timer. + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := strings.ToLower(mockSigner.PublicKey().Address().String()) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + nodeAddress, + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + existingSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 10}, + }, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 1, + SessionData: "", + } + + currentAllocations := map[string]map[string]decimal.Decimal{ + participant1: {"USDC": decimal.NewFromInt(100)}, + } + + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(60)}, + }, + SessionData: "", + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdateCore) + + reqPayload := rpc.AppSessionsV1SubmitAppStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: "60"}, + }, + SessionData: "", + }, + QuorumSigs: []string{sig1}, + } + + 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) + mockStore.On("RecordLedgerEntry", participant1, appSessionID, "USDC", decimal.NewFromInt(-40)).Return(nil) + + homeChannelID := "0xHomeChannel" + existingUserState := core.State{ + Asset: "USDC", + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(200), + UserNetFlow: decimal.NewFromInt(200), + }, + } + mockStore.On("LockUserState", participant1, "USDC").Return(decimal.Zero, nil) + mockStore.On("GetLastUserState", participant1, "USDC", false).Return(existingUserState, nil) + mockStore.On("EnsureNoOngoingEscrowOperation", participant1, "USDC").Return(nil) + // Challenged receiver channel returns nil status from CheckActiveChannel + // (status > Open), so the receiver state is stored unsigned and the squash + // at HomeChannelClosed will pick it up. + mockStore.On("CheckActiveChannel", participant1, "USDC").Return("", nil, nil) + mockStore.On("RecordTransaction", mock.Anything, mock.Anything).Return(nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), mock.Anything).Return(nil) + + mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { + return session.Version == 2 && session.Status == app.AppSessionStatusOpen + })).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitAppStateMethod), payload), + } + + handler.SubmitAppState(ctx) + + require.NotNil(t, ctx.Response) + if respErr := ctx.Response.Error(); respErr != nil { + t.Fatalf("Unexpected error response: %v", respErr) + } + require.Nil(t, capturedState.NodeSig, "Node signature must NOT be present when home channel is Challenged") + mockStatePacker.AssertNotCalled(t, "PackState", mock.Anything) + mockStore.AssertExpectations(t) +} + +func TestSubmitAppState_WithdrawIntent_ReceiverWithEscrowLock_Rejected(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() storeTxProvider := func(fn StoreTxHandler) error { return fn(mockStore) @@ -384,8 +542,130 @@ func TestSubmitAppState_CloseIntent_Success(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + false, // appRegistryEnabled=false + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + existingSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 10}, + }, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 1, + SessionData: "", + } + + currentAllocations := map[string]map[string]decimal.Decimal{ + participant1: { + "USDC": decimal.NewFromInt(100), + }, + } + + // Build the core app state update for signing + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(60)}, + }, + SessionData: "", + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdateCore) + + reqPayload := rpc.AppSessionsV1SubmitAppStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: "60"}, + }, + SessionData: "", + }, + QuorumSigs: []string{sig1}, + } + + // 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) + mockStore.On("RecordLedgerEntry", participant1, appSessionID, "USDC", decimal.NewFromInt(-40)).Return(nil) + + // Mock expectations for channel state issuance (issueReleaseReceiverState) + homeChannelID := "0xHomeChannel" + existingUserState := core.State{ + Asset: "USDC", + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(200), + UserNetFlow: decimal.NewFromInt(200), + }, + } + + mockStore.On("LockUserState", participant1, "USDC").Return(decimal.Zero, nil) + mockStore.On("GetLastUserState", participant1, "USDC", false).Return(existingUserState, nil) + mockStore.On("EnsureNoOngoingEscrowOperation", participant1, "USDC").Return(fmt.Errorf("escrow lock is still ongoing")) + + // Create RPC context + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitAppStateMethod), payload), + } + + // Execute + handler.SubmitAppState(ctx) + + // Assert - should fail because participant has an active escrow lock + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error when participant has active escrow lock") + assert.Contains(t, respErr.Error(), "escrow lock is still ongoing") + + mockStore.AssertExpectations(t) +} + +func TestSubmitAppState_CloseIntent_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := strings.ToLower(mockSigner.PublicKey().Address().String()) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + nodeAddress, + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -450,22 +730,53 @@ func TestSubmitAppState_CloseIntent_Success(t *testing.T) { mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil) + homeChannelID := "0xHomeChannel" + existingUserState1 := core.State{ + Asset: "USDC", + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(200), + UserNetFlow: decimal.NewFromInt(200), + }, + } + existingUserState2 := core.State{ + Asset: "USDC", + UserWallet: participant2, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(100), + UserNetFlow: decimal.NewFromInt(100), + }, + } + // Mock expectations for fund release and channel state issuance on close // Participant 1: 100 USDC mockStore.On("RecordLedgerEntry", participant1, appSessionID, "USDC", decimal.NewFromInt(-100)).Return(nil) mockStore.On("LockUserState", participant1, "USDC").Return(decimal.Zero, nil) - mockStore.On("GetLastUserState", participant1, "USDC", false).Return(nil, nil) - mockStore.On("GetLastUserState", participant1, "USDC", true).Return(nil, nil) + mockStore.On("GetLastUserState", participant1, "USDC", false).Return(existingUserState1, nil) + mockStore.On("EnsureNoOngoingEscrowOperation", participant1, "USDC").Return(nil) mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) - mockStore.On("RecordTransaction", mock.Anything).Return(nil) - mockStore.On("StoreUserState", mock.Anything).Return(nil).Once() + mockStore.On("RecordTransaction", mock.Anything, mock.Anything).Return(nil) + mockStore.On("CheckActiveChannel", participant1, "USDC").Return("0x03", core.ChannelStatusOpen, nil) // Participant 2: 50 USDC mockStore.On("RecordLedgerEntry", participant2, appSessionID, "USDC", decimal.NewFromInt(-50)).Return(nil) mockStore.On("LockUserState", participant2, "USDC").Return(decimal.Zero, nil) - mockStore.On("GetLastUserState", participant2, "USDC", false).Return(nil, nil) - mockStore.On("GetLastUserState", participant2, "USDC", true).Return(nil, nil) - mockStore.On("StoreUserState", mock.Anything).Return(nil).Once() + mockStore.On("GetLastUserState", participant2, "USDC", false).Return(existingUserState2, nil) + mockStore.On("EnsureNoOngoingEscrowOperation", participant2, "USDC").Return(nil) + mockStore.On("CheckActiveChannel", participant2, "USDC").Return("0x03", core.ChannelStatusOpen, nil) + + // Capture stored states to verify node signatures + var capturedStates []core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedStates = append(capturedStates, state) + return true + }), mock.Anything).Return(nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { return session.Version == 2 && session.Status == app.AppSessionStatusClosed @@ -490,6 +801,13 @@ func TestSubmitAppState_CloseIntent_Success(t *testing.T) { } assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) + // Verify node signatures on all stored channel states + require.Len(t, capturedStates, 2, "Expected 2 stored states for 2 participants") + for _, state := range capturedStates { + require.NotNil(t, state.NodeSig, "Node signature should be present on stored state for %s", state.UserWallet) + VerifyNodeSignature(t, nodeAddress, []byte("packed"), *state.NodeSig) + } + mockStore.AssertExpectations(t) } @@ -500,7 +818,7 @@ func TestSubmitAppState_CloseIntent_AllocationMismatch_Rejected(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -512,8 +830,9 @@ func TestSubmitAppState_CloseIntent_AllocationMismatch_Rejected(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -599,7 +918,7 @@ func TestSubmitAppState_OperateIntent_MissingAllocation_Rejected(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -611,8 +930,9 @@ func TestSubmitAppState_OperateIntent_MissingAllocation_Rejected(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -707,7 +1027,7 @@ func TestSubmitAppState_OperateIntent_MissingAllocation_Rejected(t *testing.T) { func TestSubmitAppState_WithdrawIntent_MissingAllocation_Rejected(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() storeTxProvider := func(fn StoreTxHandler) error { return fn(mockStore) @@ -724,8 +1044,9 @@ func TestSubmitAppState_WithdrawIntent_MissingAllocation_Rejected(t *testing.T) core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -790,9 +1111,9 @@ func TestSubmitAppState_WithdrawIntent_MissingAllocation_Rejected(t *testing.T) mockStore.On("RecordLedgerEntry", participant1, appSessionID, "USDC", decimal.NewFromInt(-40)).Return(nil).Maybe() mockStore.On("LockUserState", participant1, "USDC").Return(decimal.Zero, nil).Maybe() mockStore.On("GetLastUserState", participant1, "USDC", false).Return(nil, nil).Maybe() - mockStore.On("GetLastUserState", participant1, "USDC", true).Return(nil, nil).Maybe() - mockStore.On("StoreUserState", mock.Anything).Return(nil).Maybe() - mockStore.On("RecordTransaction", mock.Anything).Return(nil).Maybe() + mockStore.On("EnsureNoOngoingEscrowOperation", participant1, "USDC").Return(nil).Maybe() + mockStore.On("StoreUserState", mock.Anything, mock.Anything).Return(nil).Maybe() + mockStore.On("RecordTransaction", mock.Anything, mock.Anything).Return(nil).Maybe() // Create RPC context payload, err := rpc.NewPayload(reqPayload) @@ -823,7 +1144,7 @@ func TestSubmitAppState_DepositIntent_Rejected(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -835,8 +1156,9 @@ func TestSubmitAppState_DepositIntent_Rejected(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -878,7 +1200,7 @@ func TestSubmitAppState_ClosedSession_Rejected(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -890,8 +1212,9 @@ func TestSubmitAppState_ClosedSession_Rejected(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -948,7 +1271,7 @@ func TestSubmitAppState_InvalidVersion_Rejected(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -960,8 +1283,9 @@ func TestSubmitAppState_InvalidVersion_Rejected(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -1023,7 +1347,7 @@ func TestSubmitAppState_SessionNotFound_Rejected(t *testing.T) { return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -1035,8 +1359,9 @@ func TestSubmitAppState_SessionNotFound_Rejected(t *testing.T) { core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -1083,7 +1408,7 @@ func TestSubmitAppState_OperateIntent_InvalidDecimalPrecision_Rejected(t *testin return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -1095,8 +1420,9 @@ func TestSubmitAppState_OperateIntent_InvalidDecimalPrecision_Rejected(t *testin core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -1186,7 +1512,7 @@ func TestSubmitAppState_OperateIntent_InvalidDecimalPrecision_Rejected(t *testin func TestSubmitAppState_WithdrawIntent_InvalidDecimalPrecision_Rejected(t *testing.T) { // Setup mockStore := new(MockStore) - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() storeTxProvider := func(fn StoreTxHandler) error { return fn(mockStore) @@ -1203,8 +1529,9 @@ func TestSubmitAppState_WithdrawIntent_InvalidDecimalPrecision_Rejected(t *testi core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -1295,7 +1622,7 @@ func TestSubmitAppState_OperateIntent_RedistributeToNewParticipant_Success(t *te return fn(mockStore) } - mockSigner := NewMockSigner() + mockSigner := NewMockChannelSigner() mockAssetStore := new(MockAssetStore) mockStatePacker := new(MockStatePacker) @@ -1307,8 +1634,9 @@ func TestSubmitAppState_OperateIntent_RedistributeToNewParticipant_Success(t *te core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, "0xNode", + true, metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, + 32, 1024, 256, 16, 100, ) appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" @@ -1407,3 +1735,400 @@ func TestSubmitAppState_OperateIntent_RedistributeToNewParticipant_Success(t *te mockStore.AssertExpectations(t) mockAssetStore.AssertExpectations(t) } + +// TestSubmitAppState_AppRegistryDisabled verifies that when appRegistryEnabled=false, +// app lookup and AllowAction are skipped but the operate intent still succeeds. +func TestSubmitAppState_AppRegistryDisabled(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{Err: errors.New("should not be called")}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xNode", + false, // appRegistryEnabled=false + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + participant2 := "0x2222222222222222222222222222222222222222" + + existingSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 5}, + {WalletAddress: participant2, SignatureWeight: 5}, + }, + Quorum: 5, + Status: app.AppSessionStatusOpen, + Version: 1, + SessionData: `{"state":"initial"}`, + } + + currentAllocations := map[string]map[string]decimal.Decimal{ + participant1: {"USDC": decimal.NewFromInt(100)}, + participant2: {"USDC": decimal.NewFromInt(50)}, + } + + sessionBalances := map[string]decimal.Decimal{ + "USDC": decimal.NewFromInt(150), + } + + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(100)}, + {Participant: participant2, Asset: "USDC", Amount: decimal.NewFromInt(50)}, + }, + SessionData: `{"state":"updated"}`, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdateCore) + + reqPayload := rpc.AppSessionsV1SubmitAppStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: "100"}, + {Participant: participant2, Asset: "USDC", Amount: "50"}, + }, + SessionData: `{"state":"updated"}`, + }, + QuorumSigs: []string{sig1}, + } + + // NO GetApp mock — it should not be called + mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) + mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) + mockStore.On("GetAppSessionBalances", appSessionID).Return(sessionBalances, nil) + mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil) + mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { + return session.Version == 2 && session.SessionData == `{"state":"updated"}` + })).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitAppStateMethod), payload), + } + + handler.SubmitAppState(ctx) + + require.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) + + // Strict: GetApp must NOT have been called + mockStore.AssertNotCalled(t, "GetApp", mock.Anything) + mockStore.AssertExpectations(t) +} + +func TestSubmitAppState_WithdrawIntent_DuplicateAllocation_Rejected(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xNode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + existingSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 10}, + }, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + currentAllocations := map[string]map[string]decimal.Decimal{ + participant1: {"USDC": decimal.NewFromInt(100)}, + } + + // Duplicate (participant1, USDC) entries in withdraw intent + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(60)}, + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(40)}, // duplicate + }, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdateCore) + + reqPayload := rpc.AppSessionsV1SubmitAppStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: "60"}, + {Participant: participant1, Asset: "USDC", Amount: "40"}, // duplicate + }, + }, + QuorumSigs: []string{sig1}, + } + + 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) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitAppStateMethod), payload), + } + + handler.SubmitAppState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "expected error for duplicate allocation") + assert.Contains(t, respErr.Error(), "duplicate allocation") + + mockStore.AssertNotCalled(t, "RecordLedgerEntry", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitAppState_CloseIntent_DuplicateAllocation_Rejected(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xNode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + existingSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 10}, + }, + Quorum: 10, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + currentAllocations := map[string]map[string]decimal.Decimal{ + participant1: {"USDC": decimal.NewFromInt(100)}, + } + + // Duplicate (participant1, USDC) entries in close intent + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentClose, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(100)}, + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(100)}, // duplicate + }, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdateCore) + + reqPayload := rpc.AppSessionsV1SubmitAppStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentClose, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: "100"}, + {Participant: participant1, Asset: "USDC", Amount: "100"}, // duplicate + }, + }, + QuorumSigs: []string{sig1}, + } + + 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() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitAppStateMethod), payload), + } + + handler.SubmitAppState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "expected error for duplicate allocation") + assert.Contains(t, respErr.Error(), "duplicate allocation") + + mockStore.AssertNotCalled(t, "RecordLedgerEntry", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitAppState_OperateIntent_DuplicateAllocation_Rejected(t *testing.T) { + // Setup + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xNode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + participant2 := "0x2222222222222222222222222222222222222222" + + existingSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 5}, + {WalletAddress: participant2, SignatureWeight: 5}, + }, + Quorum: 5, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + currentAllocations := map[string]map[string]decimal.Decimal{ + participant1: {"USDC": decimal.NewFromInt(50)}, + participant2: {"USDC": decimal.NewFromInt(50)}, + } + + // Craft a malicious payload with duplicate (participant, asset) entries. + // The first entry inflates the sum to pass balance validation while + // the second overwrites the per-participant map to zero out balances. + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(100)}, // inflates sum + {Participant: participant1, Asset: "USDC", Amount: decimal.NewFromInt(0)}, // duplicate overwrites to 0 + {Participant: participant2, Asset: "USDC", Amount: decimal.NewFromInt(0)}, + }, + } + sig1 := wallet1.SignAppStateUpdate(t, appStateUpdateCore) + + reqPayload := rpc.AppSessionsV1SubmitAppStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: "USDC", Amount: "100"}, + {Participant: participant1, Asset: "USDC", Amount: "0"}, // duplicate + {Participant: participant2, Asset: "USDC", Amount: "0"}, + }, + }, + QuorumSigs: []string{sig1}, + } + + sessionBalances := map[string]decimal.Decimal{ + "USDC": decimal.NewFromInt(100), + } + + 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) + mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitAppStateMethod), payload), + } + + handler.SubmitAppState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "expected error for duplicate allocation") + assert.Contains(t, respErr.Error(), "duplicate allocation") + + // RecordLedgerEntry must never be called — the request should be rejected before ledger writes + mockStore.AssertNotCalled(t, "RecordLedgerEntry", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} diff --git a/clearnode/api/app_session_v1/submit_deposit_state.go b/nitronode/api/app_session_v1/submit_deposit_state.go similarity index 85% rename from clearnode/api/app_session_v1/submit_deposit_state.go rename to nitronode/api/app_session_v1/submit_deposit_state.go index c273c608e..15436e9c7 100644 --- a/clearnode/api/app_session_v1/submit_deposit_state.go +++ b/nitronode/api/app_session_v1/submit_deposit_state.go @@ -36,6 +36,13 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { c.Fail(err, "failed to parse app state update") return } + + reqPayload.UserState.UserWallet, err = core.NormalizeHexAddress(reqPayload.UserState.UserWallet) + if err != nil { + c.Fail(rpc.Errorf("invalid user_wallet: %v", err), "") + return + } + userState, err := unmapStateV1(reqPayload.UserState) if err != nil { c.Fail(err, "failed to parse user state") @@ -71,17 +78,19 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { 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) - } + if h.appRegistryEnabled { + 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) + 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 @@ -95,12 +104,12 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { return rpc.Errorf("user state transition must have 'commit' type, got '%s'", lastTransition.Type.String()) } - approvedSigValidators, userHasOpenChannel, err := tx.CheckOpenChannel(userState.UserWallet, userState.Asset) + approvedSigValidators, channelStatus, err := tx.CheckActiveChannel(userState.UserWallet, userState.Asset) if err != nil { - return rpc.Errorf("failed to check open channel: %v", err) + return rpc.Errorf("failed to check active channel: %v", err) } - if !userHasOpenChannel { - return rpc.Errorf("user has no open channel") + if channelStatus == nil { + return rpc.Errorf("user has no active channel") } if lastTransition.AccountID != appStateUpd.AppSessionID { @@ -180,6 +189,22 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { return rpc.Errorf("negative allocation: %s for asset %s", alloc.Amount, alloc.Asset) } + decimals, err := h.assetStore.GetAssetDecimals(alloc.Asset) + if err != nil { + return rpc.Errorf("failed to get asset decimals: %v", err) + } + + if err := core.ValidateDecimalPrecision(alloc.Amount, decimals); err != nil { + return rpc.Errorf("invalid amount for allocation with asset %s and participant %s: %w", alloc.Asset, alloc.Participant, err) + } + + // Reject duplicate (participant, asset) entries + if incomingAllocations[alloc.Participant] != nil { + if _, exists := incomingAllocations[alloc.Participant][alloc.Asset]; exists { + return rpc.Errorf("duplicate allocation for participant %s, asset %s", alloc.Participant, alloc.Asset) + } + } + participantAllocs := currentAllocations[alloc.Participant] if participantAllocs == nil { participantAllocs = make(map[string]decimal.Decimal, 0) @@ -270,7 +295,7 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { nodeSig = _nodeSig.String() userState.NodeSig = &nodeSig - if err := tx.StoreUserState(userState); err != nil { + if err := tx.StoreUserState(userState, appSession.ApplicationID); err != nil { return rpc.Errorf("failed to store user state: %v", err) } @@ -279,7 +304,7 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { return rpc.Errorf("failed to create transaction: %v", err) } - if err := tx.RecordTransaction(*transaction); err != nil { + if err := tx.RecordTransaction(*transaction, appSession.ApplicationID); err != nil { return rpc.Errorf("failed to record transaction: %v", err) } logger.Info("recorded transaction", diff --git a/nitronode/api/app_session_v1/submit_deposit_state_test.go b/nitronode/api/app_session_v1/submit_deposit_state_test.go new file mode 100644 index 000000000..76615416b --- /dev/null +++ b/nitronode/api/app_session_v1/submit_deposit_state_test.go @@ -0,0 +1,1003 @@ +package app_session_v1 + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func TestSubmitDepositState_InvalidUserWallet_Rejected(t *testing.T) { + mockStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(h StoreTxHandler) error { return h(mockStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionData: 1024, + } + + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: rpc.AppStateUpdateV1{ + AppSessionID: "0xappsession", + Version: "2", + Intent: app.AppStateUpdateIntentDeposit, + }, + UserState: rpc.StateV1{ + UserWallet: "ZZ-not-an-address", + Asset: "USDC", + Epoch: "1", + Version: "1", + Transition: rpc.TransitionV1{Amount: "0"}, + }, + QuorumSigs: []string{"0xdeadbeef"}, + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "app_sessions.v1.submit_deposit_state", Payload: payload}, + } + + handler.SubmitDepositState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "invalid user_wallet") + mockStore.AssertNotCalled(t, "LockUserState", mock.Anything, mock.Anything) +} + +func TestSubmitDepositState_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := mockSigner.PublicKey().Address().String() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + signer: mockSigner, + nodeAddress: nodeAddress, + appRegistryEnabled: true, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxParticipants: 32, + maxSessionData: 1024, + maxSessionKeyIDs: 256, + maxSignedUpdates: 16, + } + + // Test data - create one key for both app session and channel state signing + userRawSigner := NewMockSigner() + channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) + appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) + participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) + participant2 := "0x2222222222222222222222222222222222222222" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + depositAmount := decimal.NewFromInt(100) + appSessionID := "0xAppSession123" + + // Create existing app session + existingAppSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + { + WalletAddress: participant1, + SignatureWeight: 1, + }, + { + WalletAddress: participant2, + SignatureWeight: 1, + }, + }, + Quorum: 1, + Nonce: 12345, + Status: app.AppSessionStatusOpen, + Version: 1, + SessionData: "", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + // Create user's current state (before deposit) + currentUserState := core.State{ + ID: core.GetStateID(participant1, asset, 1, 1), + Transition: core.Transition{ + Type: core.TransitionTypeVoid, + }, + Asset: asset, + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + EscrowLedger: nil, + UserSig: nil, + NodeSig: nil, + } + + // Create incoming user state (with commit transition) + incomingUserState := currentUserState.NextState() + + _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) + require.NoError(t, err) + + // Sign the incoming user state with channel wallet signer (adds 0x01 prefix) + mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) + packedUserState, _ := mockStatePacker.PackState(*incomingUserState) + userSig, _ := channelWalletSigner.Sign(packedUserState) + userSigStr := userSig.String() + incomingUserState.UserSig = &userSigStr + + // Create app state update and sign with app wallet signer (includes 0xA1 prefix for verifyQuorum) + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + { + Participant: participant1, + Asset: asset, + Amount: depositAmount, + }, + }, + SessionData: `{"updated": "data"}`, + } + packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) + appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) + appSigHex := hexutil.Encode(appSigBytes) + + appStateUpdate := rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: "2", // Next version + Allocations: []rpc.AppAllocationV1{ + { + Participant: participant1, + Asset: asset, + Amount: depositAmount.String(), + }, + }, + SessionData: `{"updated": "data"}`, + } + + // Mock expectations + mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Once() + mockStore.On("CheckActiveChannel", participant1, asset).Return("0x03", core.ChannelStatusOpen, nil).Once() + 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) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + 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 + mockStore.On("GetParticipantAllocations", appSessionID).Return( + map[string]map[string]decimal.Decimal{}, + nil, + ).Once() + + // Mock ledger entry recording + mockStore.On("RecordLedgerEntry", participant1, appSessionID, asset, depositAmount).Return(nil).Once() + + // Mock app session update + mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { + return session.SessionID == appSessionID && + session.Version == 2 && + session.SessionData == `{"updated": "data"}` + })).Return(nil).Once() + + // Mock user state storage + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == participant1 && + state.Version == incomingUserState.Version && + state.Transition.Type == core.TransitionTypeCommit && + state.NodeSig != nil + }), mock.Anything).Return(nil).Once() + + // Mock transaction recording + mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + return tx.TxType == core.TransactionTypeCommit && + tx.Amount.Equal(depositAmount) && + tx.ToAccount == appSessionID + }), mock.Anything).Return(nil).Once() + + // Create RPC request + rpcState := toRPCState(*incomingUserState) + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: appStateUpdate, + QuorumSigs: []string{appSigHex}, + UserState: rpcState, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), + } + + // Execute + handler.SubmitDepositState(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Check for errors first + if respErr := ctx.Response.Error(); respErr != nil { + t.Fatalf("Unexpected error response: %v", respErr) + } + + assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) + + // Parse response + var response rpc.AppSessionsV1SubmitDepositStateResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.NotEmpty(t, response.StateNodeSig, "Node signature should be present") + + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, []byte("packed"), response.StateNodeSig) + + // Verify all mock expectations + mockStore.AssertExpectations(t) +} + +func TestSubmitDepositState_InvalidTransitionType(t *testing.T) { + // Setup + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := mockSigner.PublicKey().Address().String() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + signer: mockSigner, + nodeAddress: nodeAddress, + appRegistryEnabled: true, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxParticipants: 32, + maxSessionData: 1024, + maxSessionKeyIDs: 256, + maxSignedUpdates: 16, + } + + // Test data + participant1 := "0x1111111111111111111111111111111111111111" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + appSessionID := "0xAppSession123" + + // Create user state with WRONG transition type (transfer_send instead of commit) + userState := core.State{ + ID: core.GetStateID(participant1, asset, 1, 2), + Asset: asset, + UserWallet: participant1, + Epoch: 1, + Version: 2, + Transition: core.Transition{ + Type: core.TransitionTypeTransferSend, // Wrong type! + TxID: "tx-id", + AccountID: appSessionID, + Amount: decimal.NewFromInt(100), + }, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(400), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(-100), + }, + } + + // Sign the user state + userKey, _ := crypto.GenerateKey() + mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) + packedUserState, _ := mockStatePacker.PackState(userState) + userSigBytes, _ := crypto.Sign(crypto.Keccak256Hash(packedUserState).Bytes(), userKey) + userSigHex := hexutil.Encode(userSigBytes) + userState.UserSig = &userSigHex + + // Create app state update with proper hex signature (though we'll fail before signature check) + appSigKey, _ := crypto.GenerateKey() + depositAmt := decimal.NewFromInt(100) + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + { + Participant: participant1, + Asset: asset, + Amount: depositAmt, + }, + }, + SessionData: "", + } + packedAppStateUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) + appSigBytes, _ := crypto.Sign(crypto.Keccak256Hash(packedAppStateUpdate).Bytes(), appSigKey) + appSigHex := hexutil.Encode(appSigBytes) + + appStateUpdate := rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + { + Participant: participant1, + Asset: asset, + Amount: "100", + }, + }, + } + + // 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) + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: appStateUpdate, + QuorumSigs: []string{appSigHex}, + UserState: rpcState, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), + } + + // Execute + handler.SubmitDepositState(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Verify response contains error + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "commit") + + // Verify no mocks were called since we fail early + mockStore.AssertExpectations(t) +} + +func TestSubmitDepositState_QuorumNotMet(t *testing.T) { + // Setup + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := mockSigner.PublicKey().Address().String() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + signer: mockSigner, + nodeAddress: nodeAddress, + appRegistryEnabled: true, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxParticipants: 32, + maxSessionData: 1024, + maxSessionKeyIDs: 256, + maxSignedUpdates: 16, + } + + // Test data - create one key for both app session and channel state signing + userRawSigner := NewMockSigner() + channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) + appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) + participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) + participant2 := "0x2222222222222222222222222222222222222222" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + depositAmount := decimal.NewFromInt(100) + appSessionID := "0xAppSession123" + + // Create existing app session with higher quorum requirement + existingAppSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + { + WalletAddress: participant1, + SignatureWeight: 1, + }, + { + WalletAddress: participant2, + SignatureWeight: 1, + }, + }, + Quorum: 2, // Need both signatures + Nonce: 12345, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + // Create user state + currentUserState := core.State{ + ID: core.GetStateID(participant1, asset, 1, 1), + Transition: core.Transition{ + Type: core.TransitionTypeVoid, + }, + Asset: asset, + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + } + + incomingUserState := currentUserState.NextState() + + _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) + require.NoError(t, err) + + mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) + packedUserState, _ := mockStatePacker.PackState(*incomingUserState) + userSig, _ := channelWalletSigner.Sign(packedUserState) + userSigStr := userSig.String() + incomingUserState.UserSig = &userSigStr + + // Create app state update and sign with app wallet signer (includes 0xA1 prefix for verifyQuorum) + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + { + Participant: participant1, + Asset: asset, + Amount: depositAmount, + }, + }, + SessionData: "", + } + packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) + appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) + appSigHex := hexutil.Encode(appSigBytes) + + appStateUpdate := rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + { + Participant: participant1, + Asset: asset, + Amount: depositAmount.String(), + }, + }, + } + + // Mock expectations + mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Once() + mockStore.On("CheckActiveChannel", participant1, asset).Return("0x03", core.ChannelStatusOpen, nil).Once() + 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) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + 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 + rpcState := toRPCState(*incomingUserState) + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: appStateUpdate, + QuorumSigs: []string{appSigHex}, + UserState: rpcState, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), + } + + // Execute + handler.SubmitDepositState(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Verify response contains error about quorum + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "quorum not met") + + // Verify all mocks were called + mockStore.AssertExpectations(t) +} + +// TestSubmitDepositState_AppRegistryDisabled verifies that when appRegistryEnabled=false, +// app lookup and AllowAction are skipped but deposit still succeeds. +func TestSubmitDepositState_AppRegistryDisabled(t *testing.T) { + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := mockSigner.PublicKey().Address().String() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + assetStore: mockAssetStore, + actionGateway: &MockActionGateway{Err: errors.New("should not be called")}, + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + signer: mockSigner, + nodeAddress: nodeAddress, + appRegistryEnabled: false, // disabled + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxParticipants: 32, + maxSessionData: 1024, + maxSessionKeyIDs: 256, + maxSignedUpdates: 16, + } + + userRawSigner := NewMockSigner() + channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) + appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) + participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) + participant2 := "0x2222222222222222222222222222222222222222" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + depositAmount := decimal.NewFromInt(100) + appSessionID := "0xAppSession123" + + existingAppSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + {WalletAddress: participant2, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + Status: app.AppSessionStatusOpen, + Version: 1, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + currentUserState := core.State{ + ID: core.GetStateID(participant1, asset, 1, 1), + Transition: core.Transition{ + Type: core.TransitionTypeVoid, + }, + Asset: asset, + UserWallet: participant1, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + } + + incomingUserState := currentUserState.NextState() + _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) + require.NoError(t, err) + + mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) + packedUserState, _ := mockStatePacker.PackState(*incomingUserState) + userSig, _ := channelWalletSigner.Sign(packedUserState) + userSigStr := userSig.String() + incomingUserState.UserSig = &userSigStr + + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: asset, Amount: depositAmount}, + }, + SessionData: `{"updated": "data"}`, + } + packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) + appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) + appSigHex := hexutil.Encode(appSigBytes) + + appStateUpdate := rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: asset, Amount: depositAmount.String()}, + }, + SessionData: `{"updated": "data"}`, + } + + // NO GetApp mock — it should not be called + mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Once() + mockStore.On("CheckActiveChannel", participant1, asset).Return("0x03", core.ChannelStatusOpen, nil).Once() + 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) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + mockStore.On("GetAppSession", appSessionID).Return(existingAppSession, nil).Once() + mockStore.On("GetParticipantAllocations", appSessionID).Return( + map[string]map[string]decimal.Decimal{}, nil, + ).Once() + mockStore.On("RecordLedgerEntry", participant1, appSessionID, asset, depositAmount).Return(nil).Once() + mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { + return session.SessionID == appSessionID && session.Version == 2 + })).Return(nil).Once() + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == participant1 && state.NodeSig != nil + }), mock.Anything).Return(nil).Once() + mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + return tx.TxType == core.TransactionTypeCommit && tx.Amount.Equal(depositAmount) + }), mock.Anything).Return(nil).Once() + + rpcState := toRPCState(*incomingUserState) + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: appStateUpdate, + QuorumSigs: []string{appSigHex}, + UserState: rpcState, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), + } + + handler.SubmitDepositState(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) + + // Strict: GetApp must NOT have been called + mockStore.AssertNotCalled(t, "GetApp", mock.Anything) + mockStore.AssertExpectations(t) +} + +func TestSubmitDepositState_DuplicateAllocation_Rejected(t *testing.T) { + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := mockSigner.PublicKey().Address().String() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + signer: mockSigner, + nodeAddress: nodeAddress, + appRegistryEnabled: true, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxParticipants: 32, + maxSessionData: 1024, + maxSessionKeyIDs: 256, + maxSignedUpdates: 16, + } + + userRawSigner := NewMockSigner() + channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) + appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) + participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) + participant2 := "0x2222222222222222222222222222222222222222" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + appSessionID := "0xAppSession123" + + existingAppSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + {WalletAddress: participant2, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + currentUserState := core.State{ + ID: core.GetStateID(participant1, asset, 1, 1), + Transition: core.Transition{Type: core.TransitionTypeVoid}, + Asset: asset, UserWallet: participant1, Epoch: 1, Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), UserNetFlow: decimal.NewFromInt(500), + }, + } + + depositAmount := decimal.NewFromInt(100) + incomingUserState := currentUserState.NextState() + _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) + require.NoError(t, err) + + mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) + packedUserState, _ := mockStatePacker.PackState(*incomingUserState) + userSig, _ := channelWalletSigner.Sign(packedUserState) + userSigStr := userSig.String() + incomingUserState.UserSig = &userSigStr + + // Duplicate (participant1, USDC) allocations — first inflates sum, second overwrites + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: asset, Amount: depositAmount}, + {Participant: participant1, Asset: asset, Amount: decimal.Zero}, // duplicate + }, + } + packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) + appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) + appSigHex := hexutil.Encode(appSigBytes) + + appStateUpdate := rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: asset, Amount: depositAmount.String()}, + {Participant: participant1, Asset: asset, Amount: "0"}, // duplicate + }, + } + + 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).Once() + mockStore.On("CheckActiveChannel", participant1, asset).Return("0x03", core.ChannelStatusOpen, nil).Once() + 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) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + mockStore.On("GetParticipantAllocations", appSessionID).Return( + map[string]map[string]decimal.Decimal{}, nil, + ).Once() + + // The first (non-duplicate) allocation may be processed before the duplicate is detected, + // so RecordLedgerEntry might be called for the first entry. Allow it. + mockStore.On("RecordLedgerEntry", participant1, appSessionID, asset, depositAmount).Return(nil).Maybe() + + rpcState := toRPCState(*incomingUserState) + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: appStateUpdate, + QuorumSigs: []string{appSigHex}, + UserState: rpcState, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), + } + + handler.SubmitDepositState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "expected error for duplicate allocation") + assert.Contains(t, respErr.Error(), "duplicate allocation") +} + +func TestSubmitDepositState_InvalidDecimalPrecision_Rejected(t *testing.T) { + mockStore := new(MockStore) + mockSigner := NewMockChannelSigner() + nodeAddress := mockSigner.PublicKey().Address().String() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + signer: mockSigner, + nodeAddress: nodeAddress, + appRegistryEnabled: true, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxParticipants: 32, + maxSessionData: 1024, + maxSessionKeyIDs: 256, + maxSignedUpdates: 16, + } + + userRawSigner := NewMockSigner() + channelWalletSigner, _ := core.NewChannelDefaultSigner(userRawSigner) + appWalletSigner, _ := app.NewAppSessionWalletSignerV1(userRawSigner) + participant1 := strings.ToLower(userRawSigner.PublicKey().Address().String()) + participant2 := "0x2222222222222222222222222222222222222222" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + appSessionID := "0xAppSession123" + + existingAppSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + {WalletAddress: participant2, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + Status: app.AppSessionStatusOpen, + Version: 1, + } + + // Amount with 7 decimal places for USDC (which has 6) + invalidAmount, _ := decimal.NewFromString("100.1234567") + depositAmount := invalidAmount + + currentUserState := core.State{ + ID: core.GetStateID(participant1, asset, 1, 1), + Transition: core.Transition{Type: core.TransitionTypeVoid}, + Asset: asset, UserWallet: participant1, Epoch: 1, Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), UserNetFlow: decimal.NewFromInt(500), + }, + } + + incomingUserState := currentUserState.NextState() + _, err := incomingUserState.ApplyCommitTransition(appSessionID, depositAmount) + require.NoError(t, err) + + mockStatePacker.On("PackState", mock.Anything).Return([]byte("packed"), nil) + packedUserState, _ := mockStatePacker.PackState(*incomingUserState) + userSig, _ := channelWalletSigner.Sign(packedUserState) + userSigStr := userSig.String() + incomingUserState.UserSig = &userSigStr + + appStateUpdateCore := app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: participant1, Asset: asset, Amount: invalidAmount}, + }, + } + packedAppUpdate, _ := app.PackAppStateUpdateV1(appStateUpdateCore) + appSigBytes, _ := appWalletSigner.Sign(packedAppUpdate) + appSigHex := hexutil.Encode(appSigBytes) + + appStateUpdate := rpc.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: "2", + Allocations: []rpc.AppAllocationV1{ + {Participant: participant1, Asset: asset, Amount: "100.1234567"}, + }, + } + + 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).Once() + mockStore.On("CheckActiveChannel", participant1, asset).Return("0x03", core.ChannelStatusOpen, nil).Once() + 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) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + mockStore.On("GetParticipantAllocations", appSessionID).Return( + map[string]map[string]decimal.Decimal{}, nil, + ).Once() + + rpcState := toRPCState(*incomingUserState) + reqPayload := rpc.AppSessionsV1SubmitDepositStateRequest{ + AppStateUpdate: appStateUpdate, + QuorumSigs: []string{appSigHex}, + UserState: rpcState, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1SubmitDepositStateMethod), payload), + } + + handler.SubmitDepositState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error for invalid decimal precision") + assert.Contains(t, respErr.Error(), "amount exceeds maximum decimal precision") +} diff --git a/nitronode/api/app_session_v1/submit_session_key_state.go b/nitronode/api/app_session_v1/submit_session_key_state.go new file mode 100644 index 000000000..62f5cf9c8 --- /dev/null +++ b/nitronode/api/app_session_v1/submit_session_key_state.go @@ -0,0 +1,189 @@ +package app_session_v1 + +import ( + "errors" + "strings" + "time" + + "github.com/layer-3/nitrolite/nitronode/store/database" + "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" +) + +// SubmitSessionKeyState processes session key state submissions for registration and updates. +func (h *Handler) SubmitSessionKeyState(c *rpc.Context) { + ctx := c.Context + logger := log.FromContext(ctx) + + var reqPayload rpc.AppSessionsV1SubmitSessionKeyStateRequest + if err := c.Request.Payload.Translate(&reqPayload); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + logger.Debug("processing session key state submission", + "userAddress", reqPayload.State.UserAddress, + "sessionKey", reqPayload.State.SessionKey, + "version", reqPayload.State.Version) + + // Convert RPC type to core type + coreState, err := unmapSessionKeyStateV1(&reqPayload.State) + if err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") + return + } + + // Validate required fields + coreState.UserAddress, err = core.NormalizeHexAddress(coreState.UserAddress) + if err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: invalid user_address: %v", err), "") + return + } + + coreState.SessionKey, err = core.NormalizeHexAddress(coreState.SessionKey) + if err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: invalid session_key: %v", err), "") + return + } + + if strings.EqualFold(coreState.UserAddress, coreState.SessionKey) { + c.Fail(rpc.Errorf("invalid_session_key_state: session_key must differ from user_address"), "") + return + } + + if coreState.Version == 0 { + c.Fail(rpc.Errorf("invalid_session_key_state: version must be greater than 0"), "") + return + } + // Past expires_at is permitted as a revocation signal. The auth path filters + // expires_at > now so a past timestamp deactivates the key immediately while keeping + // the same monotonic version sequence (a later submit with a future expires_at can + // re-activate the key). A negative unix timestamp is rejected because the + // metadata-hash packer casts int64 -> uint64 (wraps to a huge future value), which + // would cause the user-signed payload to disagree with the value persisted in the + // database — defense-in-depth even though the DB filter is the source of truth. + if coreState.ExpiresAt.Unix() < 0 { + c.Fail(rpc.Errorf("invalid_session_key_state: expires_at must be non-negative"), "") + return + } + if len(coreState.AppSessionIDs) > h.maxSessionKeyIDs { + c.Fail(rpc.Errorf("invalid_session_key_state: app_session_ids array exceeds maximum length of %d", h.maxSessionKeyIDs), "") + return + } + if len(coreState.ApplicationIDs) > h.maxSessionKeyIDs { + c.Fail(rpc.Errorf("invalid_session_key_state: application_ids array exceeds maximum length of %d", h.maxSessionKeyIDs), "") + return + } + for _, id := range coreState.ApplicationIDs { + if id != strings.ToLower(id) { + c.Fail(rpc.Errorf("invalid_session_key_state: application_ids must be lowercase, got: %s", id), "") + return + } + } + for _, id := range coreState.AppSessionIDs { + if id != strings.ToLower(id) { + c.Fail(rpc.Errorf("invalid_session_key_state: app_session_ids must be lowercase, got: %s", id), "") + return + } + } + if coreState.UserSig == "" { + c.Fail(rpc.Errorf("invalid_session_key_state: user_sig is required"), "") + return + } + if coreState.SessionKeySig == "" { + c.Fail(rpc.Errorf("invalid_session_key_state: session_key_sig is required"), "") + return + } + + // Validate both signatures: wallet's UserSig and session-key holder's SessionKeySig. + if err := app.ValidateAppSessionKeyStateV1(coreState); err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") + return + } + + // Validate version and store the session key state + now := time.Now() + err = h.useStoreInTx(func(tx Store) error { + // Lock the (user, session_key, app_session) pointer row for the duration of the tx so + // that concurrent submits for the same (user, session_key) serialize cleanly and report + // a proper "expected version" error rather than racing on the history UNIQUE constraint. + latestVersion, latestExpiresAt, err := tx.LockSessionKeyState(coreState.UserAddress, coreState.SessionKey, database.SessionKeyKindAppSession) + if err != nil { + if errors.Is(err, database.ErrSessionKeyNotAllowed) { + logger.Warn("session key registration collision", + "userAddress", coreState.UserAddress, + "sessionKey", coreState.SessionKey, + "kind", database.SessionKeyKindAppSession) + return rpc.Errorf("invalid_session_key_state: session_key not allowed") + } + return rpc.Errorf("failed to lock session key state: %v", err) + } + + // Enforce the per-user cap whenever this submit transitions the slot from inactive + // to active — i.e. a brand-new key (latestVersion == 0) or a reactivation where the + // previous latest state was already past its expires_at. A rotation/update against a + // still-active key is not counted again so legitimate rotation is never blocked, and + // a revoke submit (submitted expires_at <= now) decreases the active count so it is + // not subject to the cap either. + // + // Without the reactivation check a user at the cap can revoke key A, register fresh + // key B into the freed slot, then re-submit key A with a future expires_at — the + // `latestVersion > 0` branch would skip the cap check and leave the user above the + // cap. + // + // TODO(MF-H01-followup): the row lock above only serializes submits for the same + // (user, session_key, kind), so two concurrent submits registering *different* new keys + // for the same user can both observe the same count and both pass the check, ending up + // at most maxSessionKeysPerUser + (concurrent new-key writers - 1) keys. The cap is a + // soft DOS bound, not a hard quota — a small over-shoot under genuine concurrency is + // acceptable. If a hard quota is ever required, take a per-user advisory lock here + // (pg_advisory_xact_lock(hashtext(user_address))) before counting. + prevActive := latestVersion > 0 && latestExpiresAt.After(now) + submittedActive := coreState.ExpiresAt.After(now) + if !prevActive && submittedActive && h.maxSessionKeysPerUser > 0 { + count, err := tx.CountSessionKeysForUser(coreState.UserAddress) + if err != nil { + return rpc.Errorf("failed to count session keys for user: %v", err) + } + if count >= uint32(h.maxSessionKeysPerUser) { + return rpc.Errorf("invalid_session_key_state: user has reached the session key limit of %d", h.maxSessionKeysPerUser) + } + } + + if coreState.Version != latestVersion+1 { + return rpc.Errorf("invalid_session_key_state: expected version %d, got %d", latestVersion+1, coreState.Version) + } + + return tx.StoreAppSessionKeyState(coreState) + }) + + if err != nil { + logger.Error("failed to store session key state", "error", err) + c.Fail(err, "failed to store session key state") + return + } + + resp := rpc.AppSessionsV1SubmitSessionKeyStateResponse{} + + payload, err := rpc.NewPayload(resp) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) + if !coreState.ExpiresAt.After(now) { + logger.Info("session key revoked", + "userAddress", coreState.UserAddress, + "sessionKey", coreState.SessionKey, + "version", coreState.Version, + "expiresAt", coreState.ExpiresAt) + return + } + logger.Info("successfully stored session key state", + "userAddress", coreState.UserAddress, + "sessionKey", coreState.SessionKey, + "version", coreState.Version) +} diff --git a/nitronode/api/app_session_v1/submit_session_key_state_test.go b/nitronode/api/app_session_v1/submit_session_key_state_test.go new file mode 100644 index 000000000..d12253961 --- /dev/null +++ b/nitronode/api/app_session_v1/submit_session_key_state_test.go @@ -0,0 +1,922 @@ +package app_session_v1 + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/nitronode/store/database" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" +) + +// buildSignedSessionKeyStateReq creates a properly signed SubmitSessionKeyState request. +// signer signs the wallet UserSig; keySigner signs the SessionKeySig over the same packed +// bytes. Pass nil for keySigner to omit the field (for negative-path tests). +func buildSignedSessionKeyStateReq(t *testing.T, userAddress, sessionKey string, version uint64, applicationIDs, appSessionIDs []string, expiresAt time.Time, signer, keySigner sign.Signer) rpc.AppSessionsV1SubmitSessionKeyStateRequest { + t.Helper() + + if applicationIDs == nil { + applicationIDs = []string{} + } + if appSessionIDs == nil { + appSessionIDs = []string{} + } + + coreState := app.AppSessionKeyStateV1{ + UserAddress: strings.ToLower(userAddress), + SessionKey: strings.ToLower(sessionKey), + Version: version, + ApplicationIDs: applicationIDs, + AppSessionIDs: appSessionIDs, + ExpiresAt: expiresAt, + } + + packed, err := app.PackAppSessionKeyStateV1(coreState) + require.NoError(t, err) + + sig, err := signer.Sign(packed) + require.NoError(t, err) + + state := rpc.AppSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKey, + Version: strconv.FormatUint(version, 10), + ApplicationIDs: applicationIDs, + AppSessionIDs: appSessionIDs, + ExpiresAt: strconv.FormatInt(expiresAt.Unix(), 10), + UserSig: hexutil.Encode(sig), + } + + if keySigner != nil { + keySig, err := keySigner.Sign(packed) + require.NoError(t, err) + state.SessionKeySig = hexutil.Encode(keySig) + } + + return rpc.AppSessionsV1SubmitSessionKeyStateRequest{State: state} +} + +func TestSubmitSessionKeyState_Success(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxParticipants: 32, + maxSessionData: 1024, + maxSignedUpdates: 16, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + appIDs := []string{"0x1111111111111111111111111111111111111111111111111111111111111111"} + sessionIDs := []string{"0x2222222222222222222222222222222222222222222222222222222222222222"} + + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, appIDs, sessionIDs, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(0, time.Time{}, nil) + mockStore.On("StoreAppSessionKeyState", mock.AnythingOfType("app.AppSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) + mockStore.AssertExpectations(t) +} + +func TestSubmitSessionKeyState_ApplicationIDsExceedsMax(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 2, + } + + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeyAddress := "0x3333333333333333333333333333333333333333" + + // 3 application_ids exceeds max of 2 + appIDs := []string{ + "0x1111111111111111111111111111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222222222222222222222222222", + "0x3333333333333333333333333333333333333333333333333333333333333333", + } + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKeyAddress, + Version: "1", + ApplicationIDs: appIDs, + AppSessionIDs: []string{}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "application_ids array exceeds maximum length of 2") +} + +func TestSubmitSessionKeyState_AppSessionIDsExceedsMax(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 2, + } + + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeyAddress := "0x3333333333333333333333333333333333333333" + + // 3 app_session_ids exceeds max of 2 + sessionIDs := []string{ + "0x1111111111111111111111111111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222222222222222222222222222", + "0x3333333333333333333333333333333333333333333333333333333333333333", + } + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKeyAddress, + Version: "1", + ApplicationIDs: []string{}, + AppSessionIDs: sessionIDs, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "app_session_ids array exceeds maximum length of 2") +} + +func TestSubmitSessionKeyState_AtMaxLimit(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 2, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // Exactly at max (2) should pass validation + appIDs := []string{ + "0x1111111111111111111111111111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222222222222222222222222222", + } + sessionIDs := []string{ + "0x3333333333333333333333333333333333333333333333333333333333333333", + "0x4444444444444444444444444444444444444444444444444444444444444444", + } + + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, appIDs, sessionIDs, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(0, time.Time{}, nil) + mockStore.On("StoreAppSessionKeyState", mock.AnythingOfType("app.AppSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} + +func TestSubmitSessionKeyState_InvalidUserAddress(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: "not-an-address", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + ApplicationIDs: []string{}, + AppSessionIDs: []string{}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "invalid user_address") +} + +func TestSubmitSessionKeyState_RevokeWithPastExpiresAt(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + // expires_at in the past expresses a revoke: the same monotonic version sequence + // is preserved, the auth path filters expires_at > now so the key is deactivated. + expiresAt := time.Now().Add(-time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, nil, nil, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(0, time.Time{}, nil) + mockStore.On("StoreAppSessionKeyState", mock.AnythingOfType("app.AppSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} + +// Covers the typical revocation path: an active key (latestVersion > 0, prev expires_at in +// the future) is deactivated by submitting version+1 with a past expires_at. The per-user +// cap check is short-circuited because the previous state was already active (revoke +// decreases the active count), so CountSessionKeysForUser must not be called. +func TestSubmitSessionKeyState_RevokeExistingActiveKey(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 5, + } + + expiresAt := time.Now().Add(-time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 2, nil, nil, expiresAt, userSigner, sessionKeySigner) + + prevActiveExpiresAt := time.Now().Add(24 * time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(1, prevActiveExpiresAt, nil) + mockStore.On("StoreAppSessionKeyState", mock.AnythingOfType("app.AppSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "CountSessionKeysForUser", mock.Anything) +} + +// Covers the re-activation path: after a revoke (latestVersion > 0, prev expires_at in the +// past), submitting version+1 with a future expires_at re-activates the slot — i.e. the +// active count goes from N-1 back to N. Because the previous latest state was inactive, the +// per-user cap MUST be re-checked here so a user at the cap cannot revoke→register-new→ +// reactivate to exceed it. +func TestSubmitSessionKeyState_ReactivateAfterRevoke_BelowCapAllowed(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 5, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 3, nil, nil, expiresAt, userSigner, sessionKeySigner) + + prevRevokedExpiresAt := time.Now().Add(-time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(2, prevRevokedExpiresAt, nil) + mockStore.On("CountSessionKeysForUser", userAddress).Return(4, nil) + mockStore.On("StoreAppSessionKeyState", mock.AnythingOfType("app.AppSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} + +// Reactivating a revoked key when the user is already at the per-user cap must be rejected. +// Without this gate a user at the cap can revoke key A, register fresh key B into the freed +// slot, then re-submit key A with a future expires_at and end up above the cap. +func TestSubmitSessionKeyState_ReactivateAfterRevoke_AtCapRejected(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 3, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 3, nil, nil, expiresAt, userSigner, sessionKeySigner) + + prevRevokedExpiresAt := time.Now().Add(-time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(2, prevRevokedExpiresAt, nil) + mockStore.On("CountSessionKeysForUser", userAddress).Return(3, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session key limit of 3") + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "StoreAppSessionKeyState", mock.Anything) +} + +func TestSubmitSessionKeyState_RejectsNegativeExpiresAt(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: "0x1111111111111111111111111111111111111111", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + ApplicationIDs: []string{}, + AppSessionIDs: []string{}, + ExpiresAt: "-1", + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "expires_at must be non-negative") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitSessionKeyState_MissingUserSig(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: "0x1111111111111111111111111111111111111111", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + ApplicationIDs: []string{}, + AppSessionIDs: []string{}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "user_sig is required") +} + +func TestSubmitSessionKeyState_VersionMismatch(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + // Submit version 3 when latest is 0 (expects 1) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 3, []string{}, []string{}, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(0, time.Time{}, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), fmt.Sprintf("expected version %d, got %d", 1, 3)) + mockStore.AssertExpectations(t) +} + +func TestSubmitSessionKeyState_RejectsWhenAtUserCap(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 3, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, nil, nil, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(0, time.Time{}, nil) + mockStore.On("CountSessionKeysForUser", userAddress).Return(3, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session key limit of 3") + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "StoreAppSessionKeyState", mock.Anything) +} + +func TestSubmitSessionKeyState_AllowsUpdateForExistingKeyAtCap(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 3, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 5, nil, nil, expiresAt, userSigner, sessionKeySigner) + + prevActiveExpiresAt := time.Now().Add(24 * time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession).Return(4, prevActiveExpiresAt, nil) + mockStore.On("StoreAppSessionKeyState", mock.AnythingOfType("app.AppSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "CountSessionKeysForUser", mock.Anything) +} + +func TestSubmitSessionKeyState_RejectsNonLowercaseApplicationID(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeyAddress := "0x3333333333333333333333333333333333333333" + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKeyAddress, + Version: "1", + ApplicationIDs: []string{"App-1"}, + AppSessionIDs: []string{}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "application_ids must be lowercase, got: App-1") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitSessionKeyState_RejectsNonLowercaseAppSessionID(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeyAddress := "0x3333333333333333333333333333333333333333" + + reqPayload := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ + State: rpc.AppSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKeyAddress, + Version: "1", + ApplicationIDs: []string{}, + AppSessionIDs: []string{"Session-ABC"}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "app_session_ids must be lowercase, got: Session-ABC") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitSessionKeyState_SignatureMismatch(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + differentSigner := NewMockSigner() // sign with a different key + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + // Sign with differentSigner but claim userAddress + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, []string{}, []string{}, expiresAt, differentSigner, sessionKeySigner) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "user_sig does not match user_address") +} + +func TestSubmitSessionKeyState_RejectsUserAddressEqualsSessionKey(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // Use the wallet as its own session key — must be rejected outright. + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, userAddress, 1, nil, nil, expiresAt, userSigner, userSigner) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key must differ from user_address") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitSessionKeyState_RejectsMissingSessionKeySig(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // keySigner=nil → SessionKeySig field stays empty in the request. + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, nil, nil, expiresAt, userSigner, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key_sig is required") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitSessionKeyState_RejectsMismatchedSessionKeySig(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + otherSigner := NewMockSigner() + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // SessionKeySig produced by an unrelated key — declared session_key won't match the recovered address. + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, nil, nil, expiresAt, userSigner, otherSigner) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key_sig does not match session_key") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestSubmitSessionKeyState_RejectsForeignOwner(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, nil, nil, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindAppSession). + Return(0, time.Time{}, database.ErrSessionKeyNotAllowed) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key not allowed") + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "StoreAppSessionKeyState", mock.Anything) +} diff --git a/clearnode/api/app_session_v1/testing.go b/nitronode/api/app_session_v1/testing.go similarity index 76% rename from clearnode/api/app_session_v1/testing.go rename to nitronode/api/app_session_v1/testing.go index a1a220f3f..858f7a18a 100644 --- a/clearnode/api/app_session_v1/testing.go +++ b/nitronode/api/app_session_v1/testing.go @@ -11,7 +11,8 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/sign" @@ -73,14 +74,19 @@ func (m *MockStore) RecordLedgerEntry(userWallet, accountID, asset string, amoun return args.Error(0) } -func (m *MockStore) RecordTransaction(tx core.Transaction) error { - args := m.Called(tx) +func (m *MockStore) RecordTransaction(tx core.Transaction, applicationID string) error { + args := m.Called(tx, applicationID) return args.Error(0) } -func (m *MockStore) CheckOpenChannel(wallet, asset string) (string, bool, error) { +func (m *MockStore) CheckActiveChannel(wallet, asset string) (string, *core.ChannelStatus, error) { args := m.Called(wallet, asset) - return args.String(0), args.Bool(1), args.Error(2) + var status *core.ChannelStatus + if v := args.Get(1); v != nil { + s := v.(core.ChannelStatus) + status = &s + } + return args.String(0), status, args.Error(2) } func (m *MockStore) LockUserState(wallet, asset string) (decimal.Decimal, error) { @@ -97,8 +103,8 @@ func (m *MockStore) GetLastUserState(wallet, asset string, signed bool) (*core.S return &state, args.Error(1) } -func (m *MockStore) StoreUserState(state core.State) error { - args := m.Called(state) +func (m *MockStore) StoreUserState(state core.State, applicationID string) error { + args := m.Called(state, applicationID) return args.Error(0) } @@ -107,6 +113,25 @@ func (m *MockStore) EnsureNoOngoingStateTransitions(wallet, asset string) error return args.Error(0) } +func (m *MockStore) EnsureNoOngoingEscrowOperation(wallet, asset string) error { + args := m.Called(wallet, asset) + return args.Error(0) +} + +func (m *MockStore) LockSessionKeyState(userAddress, sessionKey string, kind database.SessionKeyKind) (uint64, time.Time, error) { + args := m.Called(userAddress, sessionKey, kind) + var expiresAt time.Time + if v := args.Get(1); v != nil { + expiresAt = v.(time.Time) + } + return uint64(args.Int(0)), expiresAt, args.Error(2) +} + +func (m *MockStore) CountSessionKeysForUser(userAddress string) (uint32, error) { + args := m.Called(userAddress) + return uint32(args.Int(0)), args.Error(1) +} + func (m *MockStore) StoreAppSessionKeyState(state app.AppSessionKeyStateV1) error { args := m.Called(state) return args.Error(0) @@ -117,16 +142,16 @@ func (m *MockStore) GetLastAppSessionKeyVersion(wallet, sessionKey string) (uint return args.Get(0).(uint64), args.Error(1) } -func (m *MockStore) GetLastAppSessionKeyStates(wallet string, sessionKey *string) ([]app.AppSessionKeyStateV1, error) { - args := m.Called(wallet, sessionKey) +func (m *MockStore) GetLastAppSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]app.AppSessionKeyStateV1, uint32, error) { + args := m.Called(wallet, sessionKey, includeInactive, limit, offset) if args.Get(0) == nil { - return nil, args.Error(1) + return nil, uint32(args.Int(1)), args.Error(2) } - return args.Get(0).([]app.AppSessionKeyStateV1), args.Error(1) + return args.Get(0).([]app.AppSessionKeyStateV1), uint32(args.Int(1)), args.Error(2) } -func (m *MockStore) GetAppSessionKeyOwner(sessionKey, appSessionId string) (string, error) { - args := m.Called(sessionKey, appSessionId) +func (m *MockStore) GetAppSessionKeyOwner(sessionKey, appSessionId, applicationId string) (string, error) { + args := m.Called(sessionKey, appSessionId, applicationId) return args.String(0), args.Error(1) } @@ -225,6 +250,14 @@ func NewMockSigner() sign.Signer { return signer } +// NewMockChannelSigner creates a mock channel signer for testing. +// It wraps a raw signer with the channel signature prefix byte. +func NewMockChannelSigner() *core.ChannelDefaultSigner { + s := NewMockSigner() + cs, _ := core.NewChannelDefaultSigner(s) + return cs +} + // TestAppSessionWallet is a test helper for creating properly signed app session signatures. // It generates a real ECDSA key pair and wraps it in an AppSessionSignerV1 (wallet type) // so that signatures include the required 0xA1 type prefix. @@ -277,3 +310,14 @@ func (w *TestAppSessionWallet) SignCreateRequest(t *testing.T, def app.AppDefini return hexutil.Encode(sig) } + +// VerifyNodeSignature verifies that a hex-encoded channel signature was produced by the expected node address. +func VerifyNodeSignature(t *testing.T, nodeAddr string, data []byte, sigHex string) { + t.Helper() + sigBytes, err := hexutil.Decode(sigHex) + require.NoError(t, err, "Failed to decode node signature") + + sigValidator := core.NewChannelSigValidator(nil) + err = sigValidator.Verify(nodeAddr, data, sigBytes) + require.NoError(t, err, "Node signature verification failed: expected signer %s", nodeAddr) +} diff --git a/clearnode/api/app_session_v1/utils.go b/nitronode/api/app_session_v1/utils.go similarity index 99% rename from clearnode/api/app_session_v1/utils.go rename to nitronode/api/app_session_v1/utils.go index 4381b2d62..974553b31 100644 --- a/clearnode/api/app_session_v1/utils.go +++ b/nitronode/api/app_session_v1/utils.go @@ -270,6 +270,7 @@ func unmapSessionKeyStateV1(state *rpc.AppSessionKeyStateV1) (app.AppSessionKeyS AppSessionIDs: appSessionIDs, ExpiresAt: time.Unix(expiresAtUnix, 0), UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, }, nil } @@ -283,6 +284,7 @@ func mapSessionKeyStateV1(state *app.AppSessionKeyStateV1) rpc.AppSessionKeyStat AppSessionIDs: state.AppSessionIDs, ExpiresAt: strconv.FormatInt(state.ExpiresAt.Unix(), 10), UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, } } diff --git a/clearnode/api/apps_v1/get_apps.go b/nitronode/api/apps_v1/get_apps.go similarity index 88% rename from clearnode/api/apps_v1/get_apps.go rename to nitronode/api/apps_v1/get_apps.go index 3ab03b4e5..65fd7a2b9 100644 --- a/clearnode/api/apps_v1/get_apps.go +++ b/nitronode/api/apps_v1/get_apps.go @@ -16,6 +16,15 @@ func (h *Handler) GetApps(c *rpc.Context) { return } + if req.OwnerWallet != nil { + normalizedOwnerWallet, err := core.NormalizeHexAddress(*req.OwnerWallet) + if err != nil { + c.Fail(rpc.Errorf("invalid owner_wallet: %v", err), "") + return + } + req.OwnerWallet = &normalizedOwnerWallet + } + var paginationParams core.PaginationParams if req.Pagination != nil { paginationParams.Offset = req.Pagination.Offset diff --git a/clearnode/api/apps_v1/get_apps_test.go b/nitronode/api/apps_v1/get_apps_test.go similarity index 71% rename from clearnode/api/apps_v1/get_apps_test.go rename to nitronode/api/apps_v1/get_apps_test.go index aaeb8ed52..988bd6eed 100644 --- a/clearnode/api/apps_v1/get_apps_test.go +++ b/nitronode/api/apps_v1/get_apps_test.go @@ -15,7 +15,7 @@ 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-1", OwnerWallet: "0x1111111111111111111111111111111111111111", 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 }, @@ -80,7 +80,7 @@ func TestGetApps_FilterByAppID(t *testing.T) { require.NotNil(t, appID) assert.Equal(t, "app-1", *appID) return []app.AppInfoV1{ - {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111", Version: 1}}, + {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111111111111111111111111111111111111111", Version: 1}}, }, core.PaginationMetadata{TotalCount: 1, Page: 1, PerPage: 50}, nil }, } @@ -112,16 +112,16 @@ 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) + assert.Equal(t, "0x1111111111111111111111111111111111111111", *ownerWallet) return []app.AppInfoV1{ - {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111", Version: 1}}, + {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111111111111111111111111111111111111111", Version: 1}}, }, core.PaginationMetadata{TotalCount: 1, Page: 1, PerPage: 50}, nil }, } handler := NewHandler(mockStore, nil, nil, 4096) - owner := "0x1111" + owner := "0x1111111111111111111111111111111111111111" reqPayload := rpc.AppsV1GetAppsRequest{OwnerWallet: &owner} payload, err := rpc.NewPayload(reqPayload) require.NoError(t, err) @@ -139,5 +139,36 @@ func TestGetApps_FilterByOwnerWallet(t *testing.T) { 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) + assert.Equal(t, "0x1111111111111111111111111111111111111111", resp.Apps[0].OwnerWallet) +} + +// TestGetApps_NormalizesOwnerWallet verifies that an owner_wallet filter submitted in +// mixed case is normalized to canonical lowercase before being passed to the store. +func TestGetApps_NormalizesOwnerWallet(t *testing.T) { + canonicalOwner := "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + mixedCaseOwner := "0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD" + + mockStore := &MockStore{ + getAppsFn: func(appID *string, ownerWallet *string, _ *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + require.NotNil(t, ownerWallet) + assert.Equal(t, canonicalOwner, *ownerWallet) + return []app.AppInfoV1{}, core.PaginationMetadata{}, nil + }, + } + + handler := NewHandler(mockStore, nil, nil, 4096) + + reqPayload := rpc.AppsV1GetAppsRequest{OwnerWallet: &mixedCaseOwner} + 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()) } diff --git a/clearnode/api/apps_v1/handler.go b/nitronode/api/apps_v1/handler.go similarity index 100% rename from clearnode/api/apps_v1/handler.go rename to nitronode/api/apps_v1/handler.go diff --git a/clearnode/api/apps_v1/interface.go b/nitronode/api/apps_v1/interface.go similarity index 95% rename from clearnode/api/apps_v1/interface.go rename to nitronode/api/apps_v1/interface.go index 06c9d94c2..106b18c36 100644 --- a/clearnode/api/apps_v1/interface.go +++ b/nitronode/api/apps_v1/interface.go @@ -1,7 +1,7 @@ package apps_v1 import ( - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/action_gateway" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" ) diff --git a/clearnode/api/apps_v1/submit_app_version.go b/nitronode/api/apps_v1/submit_app_version.go similarity index 88% rename from clearnode/api/apps_v1/submit_app_version.go rename to nitronode/api/apps_v1/submit_app_version.go index c604e76fe..d0876daa5 100644 --- a/clearnode/api/apps_v1/submit_app_version.go +++ b/nitronode/api/apps_v1/submit_app_version.go @@ -7,6 +7,7 @@ import ( "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/layer-3/nitrolite/pkg/sign" ) @@ -27,6 +28,11 @@ func (h *Handler) SubmitAppVersion(c *rpc.Context) { c.Fail(nil, "owner_wallet is required") return } + normalizedOwnerWallet, err := core.NormalizeHexAddress(req.App.OwnerWallet) + if err != nil { + c.Fail(rpc.Errorf("invalid owner_wallet: %v", err), "") + return + } if req.OwnerSig == "" { c.Fail(nil, "owner_sig is required") return @@ -49,14 +55,14 @@ func (h *Handler) SubmitAppVersion(c *rpc.Context) { } err = h.useStoreInTx(func(tx Store) error { - err := h.actionGateway.AllowAppRegistration(tx, req.App.OwnerWallet) + err := h.actionGateway.AllowAppRegistration(tx, normalizedOwnerWallet) if err != nil { return rpc.NewError(err) } appEntry := app.AppV1{ ID: strings.ToLower(req.App.ID), - OwnerWallet: strings.ToLower(req.App.OwnerWallet), + OwnerWallet: normalizedOwnerWallet, Metadata: req.App.Metadata, Version: version, CreationApprovalNotRequired: req.App.CreationApprovalNotRequired, diff --git a/clearnode/api/apps_v1/submit_app_version_test.go b/nitronode/api/apps_v1/submit_app_version_test.go similarity index 80% rename from clearnode/api/apps_v1/submit_app_version_test.go rename to nitronode/api/apps_v1/submit_app_version_test.go index 5486fec86..21536bc2a 100644 --- a/clearnode/api/apps_v1/submit_app_version_test.go +++ b/nitronode/api/apps_v1/submit_app_version_test.go @@ -211,6 +211,58 @@ func TestSubmitAppVersion_InvalidVersion(t *testing.T) { assert.Contains(t, err.Error(), "version 1") } +// TestSubmitAppVersion_NormalizesMixedCaseOwnerWallet verifies that an owner_wallet +// submitted in mixed case is normalized to the canonical lowercase form before being +// persisted and used for signature verification. +func TestSubmitAppVersion_NormalizesMixedCaseOwnerWallet(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, addr, entry.OwnerWallet, "owner wallet must be persisted as canonical lowercase form") + return nil + }, + } + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler(mockStore, storeTxProvider, &MockActionGateway{}, 4096) + + mixedCaseOwner := "0x" + strings.ToUpper(strings.TrimPrefix(addr, "0x")) + require.NotEqual(t, addr, mixedCaseOwner) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "test-app", + OwnerWallet: mixedCaseOwner, + 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_InvalidSignature(t *testing.T) { mockStore := &MockStore{} handler := newHandlerWithDefaults(mockStore) diff --git a/clearnode/api/apps_v1/testing.go b/nitronode/api/apps_v1/testing.go similarity index 96% rename from clearnode/api/apps_v1/testing.go rename to nitronode/api/apps_v1/testing.go index e3cf9a4cf..e136d77b9 100644 --- a/clearnode/api/apps_v1/testing.go +++ b/nitronode/api/apps_v1/testing.go @@ -3,7 +3,7 @@ package apps_v1 import ( "time" - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/action_gateway" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" diff --git a/clearnode/api/channel_v1/get_channels.go b/nitronode/api/channel_v1/get_channels.go similarity index 91% rename from clearnode/api/channel_v1/get_channels.go rename to nitronode/api/channel_v1/get_channels.go index f51d11043..fb74b74fb 100644 --- a/clearnode/api/channel_v1/get_channels.go +++ b/nitronode/api/channel_v1/get_channels.go @@ -16,6 +16,8 @@ func channelStatusFromString(s string) (core.ChannelStatus, error) { return core.ChannelStatusOpen, nil case "challenged": return core.ChannelStatusChallenged, nil + case "closing": + return core.ChannelStatusClosing, nil case "closed": return core.ChannelStatusClosed, nil default: @@ -46,6 +48,12 @@ func (h *Handler) GetChannels(c *rpc.Context) { c.Fail(rpc.Errorf("wallet is required"), "missing wallet") return } + normalizedWallet, err := core.NormalizeHexAddress(req.Wallet) + if err != nil { + c.Fail(rpc.Errorf("invalid wallet: %v", err), "") + return + } + req.Wallet = normalizedWallet var statusFilter *core.ChannelStatus if req.Status != nil && *req.Status != "" { @@ -89,7 +97,7 @@ func (h *Handler) GetChannels(c *rpc.Context) { var channels []core.Channel var totalCount uint32 - err := h.useStoreInTx(func(tx Store) error { + err = h.useStoreInTx(func(tx Store) error { var err error channels, totalCount, err = tx.GetUserChannels(req.Wallet, statusFilter, req.Asset, typeFilter, limit, offset) if err != nil { diff --git a/clearnode/api/channel_v1/get_channels_test.go b/nitronode/api/channel_v1/get_channels_test.go similarity index 88% rename from clearnode/api/channel_v1/get_channels_test.go rename to nitronode/api/channel_v1/get_channels_test.go index 05dc01c8d..533995d58 100644 --- a/clearnode/api/channel_v1/get_channels_test.go +++ b/nitronode/api/channel_v1/get_channels_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" ) @@ -207,7 +207,7 @@ func TestGetChannels_EmptyResult(t *testing.T) { mockTxStore := new(MockStore) handler := newGetChannelsHandler(mockTxStore) - userWallet := "0xNoChannelsUser" + userWallet := "0x0000000000000000000000000000000000000099" mockTxStore.On("GetUserChannels", userWallet, (*core.ChannelStatus)(nil), (*string)(nil), (*core.ChannelType)(nil), uint32(100), uint32(0)). Return([]core.Channel{}, uint32(0), nil) @@ -284,6 +284,33 @@ func TestGetChannels_InvalidStatus(t *testing.T) { mockTxStore.AssertExpectations(t) } +// TestGetChannels_NormalizesWallet verifies that a wallet submitted in mixed case is +// normalized to canonical lowercase before being passed to the store. +func TestGetChannels_NormalizesWallet(t *testing.T) { + mockTxStore := new(MockStore) + handler := newGetChannelsHandler(mockTxStore) + + canonicalWallet := "0x1234567890abcdef1234567890abcdef12345678" + mixedCaseWallet := "0x1234567890ABCDEF1234567890abcdef12345678" + + mockTxStore.On("GetUserChannels", canonicalWallet, (*core.ChannelStatus)(nil), (*string)(nil), (*core.ChannelType)(nil), uint32(100), uint32(0)). + Return([]core.Channel{}, uint32(0), nil) + + reqPayload := rpc.ChannelsV1GetChannelsRequest{Wallet: mixedCaseWallet} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.get_channels", Payload: payload}, + } + + handler.GetChannels(ctx) + + require.Nil(t, ctx.Response.Error()) + mockTxStore.AssertExpectations(t) +} + func TestGetChannels_StoreError(t *testing.T) { mockTxStore := new(MockStore) handler := newGetChannelsHandler(mockTxStore) diff --git a/clearnode/api/channel_v1/get_escrow_channel.go b/nitronode/api/channel_v1/get_escrow_channel.go similarity index 67% rename from clearnode/api/channel_v1/get_escrow_channel.go rename to nitronode/api/channel_v1/get_escrow_channel.go index 351e566c4..028d52fa4 100644 --- a/clearnode/api/channel_v1/get_escrow_channel.go +++ b/nitronode/api/channel_v1/get_escrow_channel.go @@ -6,6 +6,11 @@ import ( ) // GetEscrowChannel retrieves current on-chain escrow channel information. +// +// Note: when the escrow channel has been closed by the on-chain purge queue +// (no signed FINALIZE_ESCROW_DEPOSIT was received before expiry), StateVersion +// on the returned channel reflects the initiate version (N) and does not +// advance to the finalize version (N+1). func (h *Handler) GetEscrowChannel(c *rpc.Context) { var req rpc.ChannelsV1GetEscrowChannelRequest if err := c.Request.Payload.Translate(&req); err != nil { @@ -20,11 +25,6 @@ func (h *Handler) GetEscrowChannel(c *rpc.Context) { if err != nil { return rpc.Errorf("failed to get channel: %v", err) } - - if channel == nil { - return rpc.Errorf("channel_not_found") - } - return nil }) @@ -33,8 +33,10 @@ func (h *Handler) GetEscrowChannel(c *rpc.Context) { return } - response := rpc.ChannelsV1GetEscrowChannelResponse{ - Channel: coreChannelToRPC(*channel), + response := rpc.ChannelsV1GetEscrowChannelResponse{} + if channel != nil { + rpcChannel := coreChannelToRPC(*channel) + response.Channel = &rpcChannel } payload, err := rpc.NewPayload(response) diff --git a/clearnode/api/channel_v1/get_escrow_channel_test.go b/nitronode/api/channel_v1/get_escrow_channel_test.go similarity index 62% rename from clearnode/api/channel_v1/get_escrow_channel_test.go rename to nitronode/api/channel_v1/get_escrow_channel_test.go index 16ae76e61..ba63b01fb 100644 --- a/clearnode/api/channel_v1/get_escrow_channel_test.go +++ b/nitronode/api/channel_v1/get_escrow_channel_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" ) @@ -87,6 +87,7 @@ func TestGetEscrowChannel_Success(t *testing.T) { var response rpc.ChannelsV1GetEscrowChannelResponse err = ctx.Response.Payload.Translate(&response) require.NoError(t, err) + require.NotNil(t, response.Channel) assert.Equal(t, escrowChannelID, response.Channel.ChannelID) assert.Equal(t, userWallet, response.Channel.UserWallet) @@ -97,3 +98,50 @@ func TestGetEscrowChannel_Success(t *testing.T) { // Verify all mock expectations mockTxStore.AssertExpectations(t) } + +func TestGetEscrowChannel_NotFound(t *testing.T) { + mockTxStore := new(MockStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(h StoreTxHandler) error { + return h(mockTxStore) + }, + nodeSigner: nodeSigner, + nodeAddress: mockSigner.PublicKey().Address().String(), + minChallenge: 3600, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + escrowChannelID := "0xMissingEscrowChannel" + mockTxStore.On("GetChannelByID", escrowChannelID).Return(nil, nil) + + reqPayload := rpc.ChannelsV1GetEscrowChannelRequest{EscrowChannelID: escrowChannelID} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.get_escrow_channel", Payload: payload}, + } + + handler.GetEscrowChannel(ctx) + + // Absence is a successful response with channel == nil. + assert.NotNil(t, ctx.Response.Payload) + assert.Nil(t, ctx.Response.Error()) + + var response rpc.ChannelsV1GetEscrowChannelResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.Nil(t, response.Channel) + + mockTxStore.AssertExpectations(t) +} diff --git a/clearnode/api/channel_v1/get_home_channel.go b/nitronode/api/channel_v1/get_home_channel.go similarity index 63% rename from clearnode/api/channel_v1/get_home_channel.go rename to nitronode/api/channel_v1/get_home_channel.go index 18b4d6935..9a0be599c 100644 --- a/clearnode/api/channel_v1/get_home_channel.go +++ b/nitronode/api/channel_v1/get_home_channel.go @@ -13,18 +13,20 @@ func (h *Handler) GetHomeChannel(c *rpc.Context) { return } + normalizedWallet, err := core.NormalizeHexAddress(req.Wallet) + if err != nil { + c.Fail(rpc.Errorf("invalid wallet: %v", err), "") + return + } + req.Wallet = normalizedWallet + var channel *core.Channel - err := h.useStoreInTx(func(tx Store) error { + err = h.useStoreInTx(func(tx Store) error { var err error - channel, err = tx.GetActiveHomeChannel(req.Wallet, req.Asset) + channel, err = tx.GetNotClosedHomeChannel(req.Wallet, req.Asset) if err != nil { return rpc.Errorf("failed to get home channel: %v", err) } - - if channel == nil { - return rpc.Errorf("channel_not_found") - } - return nil }) @@ -33,8 +35,10 @@ func (h *Handler) GetHomeChannel(c *rpc.Context) { return } - response := rpc.ChannelsV1GetHomeChannelResponse{ - Channel: coreChannelToRPC(*channel), + response := rpc.ChannelsV1GetHomeChannelResponse{} + if channel != nil { + rpcChannel := coreChannelToRPC(*channel) + response.Channel = &rpcChannel } payload, err := rpc.NewPayload(response) diff --git a/clearnode/api/channel_v1/get_home_channel_test.go b/nitronode/api/channel_v1/get_home_channel_test.go similarity index 55% rename from clearnode/api/channel_v1/get_home_channel_test.go rename to nitronode/api/channel_v1/get_home_channel_test.go index aec4fb3dd..a85d69c98 100644 --- a/clearnode/api/channel_v1/get_home_channel_test.go +++ b/nitronode/api/channel_v1/get_home_channel_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" ) @@ -59,7 +59,7 @@ func TestGetHomeChannel_Success(t *testing.T) { } // Mock expectations - mockTxStore.On("GetActiveHomeChannel", userWallet, asset).Return(&homeChannel, nil) + mockTxStore.On("GetNotClosedHomeChannel", userWallet, asset).Return(&homeChannel, nil) // Create RPC request reqPayload := rpc.ChannelsV1GetHomeChannelRequest{ @@ -89,6 +89,7 @@ func TestGetHomeChannel_Success(t *testing.T) { var response rpc.ChannelsV1GetHomeChannelResponse err = ctx.Response.Payload.Translate(&response) require.NoError(t, err) + require.NotNil(t, response.Channel) assert.Equal(t, homeChannelID, response.Channel.ChannelID) assert.Equal(t, userWallet, response.Channel.UserWallet) @@ -133,7 +134,7 @@ func TestGetHomeChannel_NotFound(t *testing.T) { asset := "USDC" // Mock expectations - mockTxStore.On("GetActiveHomeChannel", userWallet, asset).Return(nil, nil) + mockTxStore.On("GetNotClosedHomeChannel", userWallet, asset).Return(nil, nil) // Create RPC request reqPayload := rpc.ChannelsV1GetHomeChannelRequest{ @@ -156,10 +157,87 @@ func TestGetHomeChannel_NotFound(t *testing.T) { // Execute handler.GetHomeChannel(ctx) - // Assert - assert.NotNil(t, ctx.Response.Error()) - assert.Contains(t, ctx.Response.Error().Error(), "channel_not_found") + // Assert: absence is a successful response with channel == nil + assert.NotNil(t, ctx.Response.Payload) + assert.Nil(t, ctx.Response.Error()) + + var response rpc.ChannelsV1GetHomeChannelResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.Nil(t, response.Channel) // Verify all mock expectations mockTxStore.AssertExpectations(t) } + +// TestGetHomeChannel_ClosingChannel verifies that GetHomeChannel returns the channel data +// even after an off-chain Finalize has flipped it to Closing, so the SDK can still fetch +// it before submitting the on-chain close. +func TestGetHomeChannel_ClosingChannel(t *testing.T) { + mockTxStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(h StoreTxHandler) error { return h(mockTxStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + } + + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + closingChannel := core.Channel{ + ChannelID: "0xHomeChannel123", + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + Status: core.ChannelStatusClosing, + StateVersion: 5, + } + mockTxStore.On("GetNotClosedHomeChannel", userWallet, asset).Return(&closingChannel, nil) + + payload, err := rpc.NewPayload(rpc.ChannelsV1GetHomeChannelRequest{Wallet: userWallet, Asset: asset}) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.get_home_channel", Payload: payload}, + } + + handler.GetHomeChannel(ctx) + + require.Nil(t, ctx.Response.Error(), "Closing channel must be visible to GetHomeChannel") + var response rpc.ChannelsV1GetHomeChannelResponse + require.NoError(t, ctx.Response.Payload.Translate(&response)) + assert.Equal(t, "closing", response.Channel.Status) + mockTxStore.AssertExpectations(t) +} + +// TestGetHomeChannel_NormalizesWallet verifies the wallet is normalized before the store call. +func TestGetHomeChannel_NormalizesWallet(t *testing.T) { + mockTxStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(h StoreTxHandler) error { return h(mockTxStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + } + + canonicalWallet := "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + mixedCaseWallet := "0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD" + asset := "USDC" + + homeChannel := core.Channel{ChannelID: "0xch", UserWallet: canonicalWallet, Asset: asset, Type: core.ChannelTypeHome} + mockTxStore.On("GetNotClosedHomeChannel", canonicalWallet, asset).Return(&homeChannel, nil) + + reqPayload := rpc.ChannelsV1GetHomeChannelRequest{Wallet: mixedCaseWallet, Asset: asset} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.get_home_channel", Payload: payload}, + } + + handler.GetHomeChannel(ctx) + + require.Nil(t, ctx.Response.Error()) + mockTxStore.AssertExpectations(t) +} diff --git a/nitronode/api/channel_v1/get_last_key_states.go b/nitronode/api/channel_v1/get_last_key_states.go new file mode 100644 index 000000000..69288cce6 --- /dev/null +++ b/nitronode/api/channel_v1/get_last_key_states.go @@ -0,0 +1,112 @@ +package channel_v1 + +import ( + "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. +// Mandatory pagination caps response size to prevent unbounded reads. +func (h *Handler) GetLastKeyStates(c *rpc.Context) { + ctx := c.Context + logger := log.FromContext(ctx) + + var req rpc.ChannelsV1GetLastKeyStatesRequest + if err := c.Request.Payload.Translate(&req); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + if req.UserAddress == "" { + c.Fail(rpc.Errorf("user_address is required"), "") + return + } + + var limit, offset uint32 + if req.Pagination != nil { + // The endpoint orders rows by (created_at DESC, id ASC) for stable pagination; + // callers cannot override this, so any sort value is rejected rather than silently + // ignored. PaginationParamsV1.Sort is shared across the v1 API and other endpoints + // honor it, which is why we validate here instead of dropping the field. + if req.Pagination.Sort != nil && *req.Pagination.Sort != "" { + c.Fail(rpc.Errorf("invalid_pagination: sort is not supported by get_last_key_states"), "") + return + } + if req.Pagination.Limit != nil { + limit = *req.Pagination.Limit + } + if req.Pagination.Offset != nil { + offset = *req.Pagination.Offset + } + } + if limit == 0 || limit > rpc.GetLastKeyStatesPageLimit { + limit = rpc.GetLastKeyStatesPageLimit + } + + includeInactive := req.IncludeInactive != nil && *req.IncludeInactive + + logger.Debug("retrieving channel session key states", + "userAddress", req.UserAddress, + "sessionKey", req.SessionKey, + "includeInactive", includeInactive, + "limit", limit, + "offset", offset) + + var states []core.ChannelSessionKeyStateV1 + var totalCount uint32 + + err := h.useStoreInTx(func(tx Store) error { + var err error + states, totalCount, err = tx.GetLastChannelSessionKeyStates(req.UserAddress, req.SessionKey, includeInactive, limit, offset) + return err + }) + + if err != nil { + logger.Error("failed to retrieve channel session key states", "error", err) + c.Fail(err, "failed to retrieve channel session key states") + return + } + + rpcStates := make([]rpc.ChannelSessionKeyStateV1, len(states)) + for i, state := range states { + rpcStates[i] = mapChannelSessionKeyStateV1(&state) + } + + resp := rpc.ChannelsV1GetLastKeyStatesResponse{ + States: rpcStates, + Metadata: buildPageMetadata(totalCount, limit, offset), + } + + payload, err := rpc.NewPayload(resp) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) +} + +// buildPageMetadata returns the standard pagination metadata for get_last_key_states. +// Page is 1-based and defaults to 1 (including the empty-result case, so the metadata is +// never `{page: 0, page_count: 0}`). For non-aligned offsets the page formula treats the +// offset as a row-skip count and reports the page that contains row `offset+1` — callers +// that need exact page semantics should pass offset as a multiple of limit. +func buildPageMetadata(totalCount, limit, offset uint32) rpc.PaginationMetadataV1 { + page := uint32(1) + if limit > 0 && offset >= limit { + page = (offset / limit) + 1 + } + + var pageCount uint32 + if totalCount > 0 && limit > 0 { + pageCount = (totalCount + limit - 1) / limit + } + + return rpc.PaginationMetadataV1{ + Page: page, + PerPage: limit, + TotalCount: totalCount, + PageCount: pageCount, + } +} diff --git a/nitronode/api/channel_v1/get_last_key_states_test.go b/nitronode/api/channel_v1/get_last_key_states_test.go new file mode 100644 index 000000000..d4cd37034 --- /dev/null +++ b/nitronode/api/channel_v1/get_last_key_states_test.go @@ -0,0 +1,167 @@ +package channel_v1 + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func newGetLastKeyStatesHandler(store Store) *Handler { + return &Handler{ + useStoreInTx: func(fn StoreTxHandler) error { + return fn(store) + }, + } +} + +func callGetLastKeyStates(t *testing.T, h *Handler, req rpc.ChannelsV1GetLastKeyStatesRequest) *rpc.Context { + t.Helper() + payload, err := rpc.NewPayload(req) + require.NoError(t, err) + c := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1GetLastKeyStatesMethod.String(), payload), + } + h.GetLastKeyStates(c) + return c +} + +func extractGetLastKeyStatesResponse(t *testing.T, c *rpc.Context) rpc.ChannelsV1GetLastKeyStatesResponse { + t.Helper() + require.NotNil(t, c.Response) + require.Nil(t, c.Response.Error()) + var resp rpc.ChannelsV1GetLastKeyStatesResponse + require.NoError(t, c.Response.Payload.Translate(&resp)) + return resp +} + +func TestChannelGetLastKeyStates_DefaultsToPageOneOnEmptyResult(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + mockStore.On("GetLastChannelSessionKeyStates", "0xuser", (*string)(nil), false, uint32(10), uint32(0)). + Return([]core.ChannelSessionKeyStateV1{}, 0, nil) + + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{UserAddress: "0xuser"}) + resp := extractGetLastKeyStatesResponse(t, c) + + assert.Empty(t, resp.States) + assert.Equal(t, uint32(1), resp.Metadata.Page) + assert.Equal(t, uint32(10), resp.Metadata.PerPage) + assert.Equal(t, uint32(0), resp.Metadata.TotalCount) + assert.Equal(t, uint32(0), resp.Metadata.PageCount) +} + +func TestChannelGetLastKeyStates_PaginationMetadata_AlignedOffset(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + limit := uint32(10) + offset := uint32(10) + pagination := &rpc.PaginationParamsV1{Limit: &limit, Offset: &offset} + + mockStore.On("GetLastChannelSessionKeyStates", "0xuser", (*string)(nil), false, uint32(10), uint32(10)). + Return([]core.ChannelSessionKeyStateV1{ + {UserAddress: "0xuser", SessionKey: "0xkey", Version: 1, ExpiresAt: time.Now().Add(time.Hour)}, + }, 25, nil) + + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + Pagination: pagination, + }) + resp := extractGetLastKeyStatesResponse(t, c) + + assert.Equal(t, uint32(2), resp.Metadata.Page) + assert.Equal(t, uint32(3), resp.Metadata.PageCount) + assert.Equal(t, uint32(25), resp.Metadata.TotalCount) +} + +func TestChannelGetLastKeyStates_ClampsLimitToMax(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + excessive := uint32(1000) + pagination := &rpc.PaginationParamsV1{Limit: &excessive} + + mockStore.On("GetLastChannelSessionKeyStates", "0xuser", (*string)(nil), false, rpc.GetLastKeyStatesPageLimit, uint32(0)). + Return([]core.ChannelSessionKeyStateV1{}, 0, nil) + + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + Pagination: pagination, + }) + resp := extractGetLastKeyStatesResponse(t, c) + + assert.Equal(t, rpc.GetLastKeyStatesPageLimit, resp.Metadata.PerPage) + mockStore.AssertExpectations(t) +} + +func TestChannelGetLastKeyStates_RejectsSortField(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + sort := "asc" + pagination := &rpc.PaginationParamsV1{Sort: &sort} + + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + Pagination: pagination, + }) + + require.NotNil(t, c.Response) + require.NotNil(t, c.Response.Error()) + assert.Contains(t, c.Response.Error().Error(), "sort is not supported") + mockStore.AssertNotCalled(t, "GetLastChannelSessionKeyStates", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestChannelGetLastKeyStates_RequiresUserAddress(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{}) + + require.NotNil(t, c.Response) + require.NotNil(t, c.Response.Error()) + assert.Contains(t, c.Response.Error().Error(), "user_address is required") +} + +func TestChannelGetLastKeyStates_IncludeInactiveTruePlumbsToStore(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + mockStore.On("GetLastChannelSessionKeyStates", "0xuser", (*string)(nil), true, uint32(10), uint32(0)). + Return([]core.ChannelSessionKeyStateV1{}, 0, nil) + + includeInactive := true + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + IncludeInactive: &includeInactive, + }) + _ = extractGetLastKeyStatesResponse(t, c) + + mockStore.AssertExpectations(t) +} + +func TestChannelGetLastKeyStates_IncludeInactiveFalsePlumbsToStore(t *testing.T) { + mockStore := new(MockStore) + h := newGetLastKeyStatesHandler(mockStore) + + mockStore.On("GetLastChannelSessionKeyStates", "0xuser", (*string)(nil), false, uint32(10), uint32(0)). + Return([]core.ChannelSessionKeyStateV1{}, 0, nil) + + includeInactive := false + c := callGetLastKeyStates(t, h, rpc.ChannelsV1GetLastKeyStatesRequest{ + UserAddress: "0xuser", + IncludeInactive: &includeInactive, + }) + _ = extractGetLastKeyStatesResponse(t, c) + + mockStore.AssertExpectations(t) +} diff --git a/clearnode/api/channel_v1/get_latest_state.go b/nitronode/api/channel_v1/get_latest_state.go similarity index 67% rename from clearnode/api/channel_v1/get_latest_state.go rename to nitronode/api/channel_v1/get_latest_state.go index 80000a38c..ed1abcefc 100644 --- a/clearnode/api/channel_v1/get_latest_state.go +++ b/nitronode/api/channel_v1/get_latest_state.go @@ -13,18 +13,21 @@ func (h *Handler) GetLatestState(c *rpc.Context) { return } - var state core.State - err := h.useStoreInTx(func(tx Store) error { + normalizedWallet, err := core.NormalizeHexAddress(req.Wallet) + if err != nil { + c.Fail(rpc.Errorf("invalid wallet: %v", err), "") + return + } + req.Wallet = normalizedWallet + + var state *core.State + err = h.useStoreInTx(func(tx Store) error { lastState, err := tx.GetLastUserState(req.Wallet, req.Asset, req.OnlySigned) if err != nil { return rpc.Errorf("failed to get last user state: %v", err) } - if lastState == nil { - return rpc.Errorf("channel not found") - } - - state = *lastState + state = lastState return nil }) @@ -33,8 +36,10 @@ func (h *Handler) GetLatestState(c *rpc.Context) { return } - response := rpc.ChannelsV1GetLatestStateResponse{ - State: coreStateToRPC(state), + response := rpc.ChannelsV1GetLatestStateResponse{} + if state != nil { + rpcState := coreStateToRPC(*state) + response.State = &rpcState } payload, err := rpc.NewPayload(response) diff --git a/clearnode/api/channel_v1/get_latest_state_test.go b/nitronode/api/channel_v1/get_latest_state_test.go similarity index 69% rename from clearnode/api/channel_v1/get_latest_state_test.go rename to nitronode/api/channel_v1/get_latest_state_test.go index 922bca4c8..5bebb38fd 100644 --- a/clearnode/api/channel_v1/get_latest_state_test.go +++ b/nitronode/api/channel_v1/get_latest_state_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" ) @@ -99,6 +99,7 @@ func TestGetLatestState_Success(t *testing.T) { var response rpc.ChannelsV1GetLatestStateResponse err = ctx.Response.Payload.Translate(&response) require.NoError(t, err) + require.NotNil(t, response.State) assert.Equal(t, state.ID, response.State.ID) assert.Equal(t, userWallet, response.State.UserWallet) @@ -198,6 +199,7 @@ func TestGetLatestState_OnlySigned(t *testing.T) { var response rpc.ChannelsV1GetLatestStateResponse err = ctx.Response.Payload.Translate(&response) require.NoError(t, err) + require.NotNil(t, response.State) assert.Equal(t, state.ID, response.State.ID) assert.Equal(t, "3", response.State.Version) @@ -205,3 +207,70 @@ func TestGetLatestState_OnlySigned(t *testing.T) { // Verify all mock expectations mockTxStore.AssertExpectations(t) } + +// TestGetLatestState_NormalizesWallet verifies the wallet is normalized before the store call. +func TestGetLatestState_NormalizesWallet(t *testing.T) { + mockTxStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(h StoreTxHandler) error { return h(mockTxStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + } + + canonicalWallet := "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + mixedCaseWallet := "0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD" + asset := "USDC" + + state := core.State{UserWallet: canonicalWallet, Asset: asset, Version: 1} + mockTxStore.On("GetLastUserState", canonicalWallet, asset, false).Return(state, nil) + + reqPayload := rpc.ChannelsV1GetLatestStateRequest{Wallet: mixedCaseWallet, Asset: asset} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.get_latest_state", Payload: payload}, + } + + handler.GetLatestState(ctx) + + require.Nil(t, ctx.Response.Error()) + mockTxStore.AssertExpectations(t) +} + +// TestGetLatestState_NotFound verifies absent state returns a successful response with nil state payload. +func TestGetLatestState_NotFound(t *testing.T) { + mockTxStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(h StoreTxHandler) error { return h(mockTxStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + } + + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil) + + reqPayload := rpc.ChannelsV1GetLatestStateRequest{Wallet: userWallet, Asset: asset} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.get_latest_state", Payload: payload}, + } + + handler.GetLatestState(ctx) + + // Absence is a successful response with state == nil. + require.NotNil(t, ctx.Response.Payload) + require.NoError(t, ctx.Response.Error()) + + var response rpc.ChannelsV1GetLatestStateResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.Nil(t, response.State) + + mockTxStore.AssertExpectations(t) +} diff --git a/clearnode/api/channel_v1/handler.go b/nitronode/api/channel_v1/handler.go similarity index 54% rename from clearnode/api/channel_v1/handler.go rename to nitronode/api/channel_v1/handler.go index f5912303c..a9153f158 100644 --- a/clearnode/api/channel_v1/handler.go +++ b/nitronode/api/channel_v1/handler.go @@ -2,8 +2,9 @@ package channel_v1 import ( "context" + "strings" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/log" "github.com/layer-3/nitrolite/pkg/rpc" @@ -11,16 +12,18 @@ import ( // 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 - nodeAddress string // Node's wallet address for channel ID calculation - minChallenge uint32 - metrics metrics.RuntimeMetricExporter - maxSessionKeyIDs int + useStoreInTx StoreTxProvider + memoryStore MemoryStore + actionGateway ActionGateway + nodeSigner *core.ChannelDefaultSigner + stateAdvancer core.StateAdvancer + statePacker core.StatePacker + nodeAddress string // Node's wallet address for channel ID calculation + minChallenge uint32 + maxChallenge uint32 + metrics metrics.RuntimeMetricExporter + maxSessionKeyIDs int + maxSessionKeysPerUser int } // NewHandler creates a new Handler instance with the provided dependencies. @@ -32,21 +35,24 @@ func NewHandler( stateAdvancer core.StateAdvancer, statePacker core.StatePacker, nodeAddress string, - minChallenge uint32, + minChallenge, maxChallenge uint32, m metrics.RuntimeMetricExporter, maxSessionKeyIDs int, + maxSessionKeysPerUser int, ) *Handler { return &Handler{ - stateAdvancer: stateAdvancer, - statePacker: statePacker, - useStoreInTx: useStoreInTx, - memoryStore: memoryStore, - actionGateway: actionGateway, - nodeSigner: nodeSigner, - nodeAddress: nodeAddress, - minChallenge: minChallenge, - metrics: m, - maxSessionKeyIDs: maxSessionKeyIDs, + stateAdvancer: stateAdvancer, + statePacker: statePacker, + useStoreInTx: useStoreInTx, + memoryStore: memoryStore, + actionGateway: actionGateway, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + maxChallenge: maxChallenge, + metrics: m, + maxSessionKeyIDs: maxSessionKeyIDs, + maxSessionKeysPerUser: maxSessionKeysPerUser, } } @@ -59,15 +65,19 @@ func (h *Handler) getChannelSigValidator(tx Store, asset string) *core.ChannelSi // issueTransferReceiverState creates and stores a new state for the receiver of a transfer. // It reads the receiver's current state, applies a transfer_receive transition with the same // amount and tx hash, signs it with the node's key, and persists it. -func (h *Handler) issueTransferReceiverState(ctx context.Context, tx Store, senderState core.State) (*core.State, error) { +func (h *Handler) issueTransferReceiverState(ctx context.Context, tx Store, senderState core.State, applicationID string) (*core.State, error) { logger := log.FromContext(ctx) incomingTransition := senderState.Transition if incomingTransition.Type != core.TransitionTypeTransferSend { return nil, rpc.Errorf("incoming state doesn't have 'transfer_send' transition") } - receiverWallet := incomingTransition.AccountID - if senderState.UserWallet == receiverWallet { + receiverWallet, err := core.NormalizeHexAddress(incomingTransition.AccountID) + if err != nil { + return nil, rpc.Errorf("invalid receiver wallet address: %v", err) + } + + if strings.EqualFold(senderState.UserWallet, receiverWallet) { return nil, rpc.Errorf("sender and receiver wallets are the same") } @@ -85,7 +95,7 @@ func (h *Handler) issueTransferReceiverState(ctx context.Context, tx Store, send currentState, err := tx.GetLastUserState(receiverWallet, senderState.Asset, false) if err != nil { - return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s", senderState.Asset, incomingTransition.AccountID) + return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s", senderState.Asset, receiverWallet) } if currentState == nil { currentState = core.NewVoidState(senderState.Asset, receiverWallet) @@ -100,35 +110,52 @@ func (h *Handler) issueTransferReceiverState(ctx context.Context, tx Store, send return nil, err } - lastSignedState, err := tx.GetLastUserState(receiverWallet, senderState.Asset, true) - if err != nil { - return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s", senderState.Asset, incomingTransition.AccountID) + if err := tx.EnsureNoOngoingEscrowOperation(receiverWallet, senderState.Asset); err != nil { + return nil, rpc.Errorf("cannot issue transfer receiver state: %v", err) } - // TODO: move to DB query - shouldSign := true - if lastSignedState != nil { - lastStateTransition := lastSignedState.Transition - if lastStateTransition.Type == core.TransitionTypeMutualLock || - lastStateTransition.Type == core.TransitionTypeEscrowLock { - shouldSign = false - } - } - - if newState.HomeChannelID != nil && shouldSign { - packedState, err := h.statePacker.PackState(*newState) + if newState.HomeChannelID != nil { + // CheckActiveChannel returns a non-nil status only when an Open or Void home + // channel exists for (wallet, asset); Challenged / Closing / Closed channels + // fall through as nil. The node only attaches a signature on Open: any other + // status means the channel is mid-dispute or terminal, and node-signing a + // receiver state on it could turn dust credits into a fresh checkpoint + // candidate (Challenged) or commit a credit that will never settle (Closed, + // Closing). The unsigned row is still persisted so the challenge-rescue + // squash at close can pick it up. + // + // MF3-I01: persisting an unsigned row whose HomeChannelID still references + // a now-Closed channel is safe under the listener ordering & idempotency + // invariant (pkg/blockchain/evm/listener.go, processEvents doc). For any + // Path-1 (challenge-timeout) close, HandleHomeChannelChallenged has + // already run before HandleHomeChannelClosed, so the rescue issued from + // HandleHomeChannelClosed has overwritten currentState with a fresh-epoch + // row whose HomeChannelID is nil, and the next call here reads that row + // rather than the wedge state. The unsigned credit issued here can only + // land on a Closed channel if it commits before the close handler runs + // — in which case the close handler's rescue sum picks it up. + _, channelStatus, err := tx.CheckActiveChannel(receiverWallet, senderState.Asset) if err != nil { - return nil, rpc.Errorf("failed to pack receiver state: %v", err) + return nil, rpc.Errorf("failed to check receiver active channel: %v", err) } - - _nodeSig, err := h.nodeSigner.Sign(packedState) - if err != nil { - return nil, rpc.Errorf("failed to sign receiver state") + if channelStatus != nil && *channelStatus == core.ChannelStatusOpen { + packedState, err := h.statePacker.PackState(*newState) + if err != nil { + return nil, rpc.Errorf("failed to pack receiver state: %v", err) + } + + _nodeSig, err := h.nodeSigner.Sign(packedState) + if err != nil { + return nil, rpc.Errorf("failed to sign receiver state") + } + nodeSig := _nodeSig.String() + newState.NodeSig = &nodeSig + } else { + logger.Info("skipping node signature on receiver state for non-open home channel", + "homeChannelID", *newState.HomeChannelID) } - nodeSig := _nodeSig.String() - newState.NodeSig = &nodeSig } - if err := tx.StoreUserState(*newState); err != nil { + if err := tx.StoreUserState(*newState, applicationID); err != nil { return nil, rpc.Errorf("failed to store receiver state") } diff --git a/clearnode/api/channel_v1/interface.go b/nitronode/api/channel_v1/interface.go similarity index 62% rename from clearnode/api/channel_v1/interface.go rename to nitronode/api/channel_v1/interface.go index 3817e275a..56b50d2ac 100644 --- a/clearnode/api/channel_v1/interface.go +++ b/nitronode/api/channel_v1/interface.go @@ -1,7 +1,10 @@ package channel_v1 import ( - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "time" + + "github.com/layer-3/nitrolite/nitronode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" ) @@ -27,24 +30,36 @@ type Store interface { // Returns nil state if no matching state exists. GetLastUserState(wallet, asset string, signed bool) (*core.State, error) - // CheckOpenChannel verifies if a user has an active channel for the given asset - // and returns the approved signature validators if such a channel exists. - CheckOpenChannel(wallet, asset string) (string, bool, error) + // CheckActiveChannel verifies if a user has an active home channel for the given asset + // and returns its approved signature validators and current status. A nil status means + // no active channel exists. "Active" includes Void (DB-only, awaiting onchain confirmation) + // and Open (materialized onchain); callers needing onchain materialization must additionally + // require Status == core.ChannelStatusOpen. + CheckActiveChannel(wallet, asset string) (string, *core.ChannelStatus, error) // StoreUserState persists a new user state to the database. - StoreUserState(state core.State) error + // applicationID is the client-declared origin tag (rpc.ApplicationIDQueryParam); + // empty string is persisted as NULL. + StoreUserState(state core.State, applicationID string) error // EnsureNoOngoingStateTransitions validates that no blockchain operations are pending // that would conflict with submitting a new state transition. EnsureNoOngoingStateTransitions(wallet, asset string) error + // EnsureNoOngoingEscrowOperation validates that the user has no in-flight escrow + // operation (escrow_lock, mutual_lock, or unfinalized escrow_deposit/escrow_withdraw) + // that would prevent issuing a receiver-side state. + EnsureNoOngoingEscrowOperation(wallet, asset string) error + // ScheduleInitiateEscrowWithdrawal queues a blockchain action to initiate // withdrawal from an escrow channel (triggered by escrow_lock transition). ScheduleInitiateEscrowWithdrawal(stateID string, chainID uint64) error // RecordTransaction creates a transaction record linking state transitions // to track the history of operations (deposits, withdrawals, transfers, etc.). - RecordTransaction(tx core.Transaction) error + // applicationID is the client-declared origin tag (rpc.ApplicationIDQueryParam); + // empty string is persisted as NULL. + RecordTransaction(tx core.Transaction, applicationID string) error // CreateChannel creates a new channel entity in the database. // This is called during channel creation before the channel exists on-chain. @@ -59,11 +74,35 @@ type Store interface { // Returns nil if no home channel exists for the given wallet and asset. GetActiveHomeChannel(wallet, asset string) (*core.Channel, error) + // GetNotClosedHomeChannel retrieves the home channel regardless of status as long as it + // is not Closed. Used by GetHomeChannel so the endpoint stays functional after an + // off-chain Finalize flips the channel to Closing. + GetNotClosedHomeChannel(wallet, asset string) (*core.Channel, error) + + // UpdateChannel persists changes to a channel's metadata (status, version, etc). + // The channel must already exist in the database. + UpdateChannel(channel core.Channel) error + + // HasNonClosedHomeChannel returns true if any home channel for (wallet, asset) has a + // status other than Closed, meaning a channel lifecycle is still in progress. + HasNonClosedHomeChannel(wallet, asset string) (bool, error) + // GetUserChannels retrieves all channels for a user with optional status, asset, and type filters. GetUserChannels(wallet string, status *core.ChannelStatus, asset *string, channelType *core.ChannelType, limit, offset uint32) ([]core.Channel, uint32, error) // Session key state operations + // LockSessionKeyState locks the (user, session_key, kind) pointer row for the surrounding + // transaction, returning the current version (0 if newly created) and the expires_at of + // the matching history row (zero time when version is 0). Callers use the expires_at to + // distinguish a reactivation (prev inactive → submitted active) from a rotation, so the + // per-user cap can be re-checked when a revoked slot is brought back. + LockSessionKeyState(userAddress, sessionKey string, kind database.SessionKeyKind) (latestVersion uint64, latestExpiresAt time.Time, err error) + + // CountSessionKeysForUser returns the number of distinct session keys for the wallet + // across both kinds, used to enforce the per-user cap at submit time. + CountSessionKeysForUser(userAddress string) (uint32, error) + // StoreChannelSessionKeyState persists a channel session key state. StoreChannelSessionKeyState(state core.ChannelSessionKeyStateV1) error @@ -72,8 +111,10 @@ type Store interface { GetLastChannelSessionKeyVersion(wallet, sessionKey string) (uint64, error) // GetLastChannelSessionKeyStates retrieves the latest channel session key states for a user, - // optionally filtered by session key. - GetLastChannelSessionKeyStates(wallet string, sessionKey *string) ([]core.ChannelSessionKeyStateV1, error) + // optionally filtered by session key. When includeInactive is false, only non-expired latest + // states are returned; when true, all latest states are returned regardless of expiry. + // Results are paginated. + GetLastChannelSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]core.ChannelSessionKeyStateV1, uint32, error) // ValidateChannelSessionKeyForAsset checks that a valid, non-expired session key state // exists at its latest version for the (wallet, sessionKey) pair, includes the given asset, diff --git a/clearnode/api/channel_v1/request_creation.go b/nitronode/api/channel_v1/request_creation.go similarity index 74% rename from clearnode/api/channel_v1/request_creation.go rename to nitronode/api/channel_v1/request_creation.go index 99fd79fa1..53b8e00df 100644 --- a/clearnode/api/channel_v1/request_creation.go +++ b/nitronode/api/channel_v1/request_creation.go @@ -4,7 +4,6 @@ import ( "strings" "time" - "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" @@ -24,8 +23,10 @@ func (h *Handler) RequestCreation(c *rpc.Context) { return } - if !common.IsHexAddress(reqPayload.State.UserWallet) { - c.Fail(rpc.Errorf("invalid user_wallet address"), "") + var err error + reqPayload.State.UserWallet, err = core.NormalizeHexAddress(reqPayload.State.UserWallet) + if err != nil { + c.Fail(rpc.Errorf("invalid user_wallet: %v", err), "") return } @@ -41,6 +42,19 @@ func (h *Handler) RequestCreation(c *rpc.Context) { return } + if channelDef.Nonce == 0 { + c.Fail(nil, "nonce must be non-zero") + return + } + if channelDef.Challenge < h.minChallenge { + c.Fail(rpc.Errorf("challenge period must be at least %d seconds, but got %d", h.minChallenge, channelDef.Challenge), "") + return + } + if channelDef.Challenge > h.maxChallenge { + c.Fail(rpc.Errorf("challenge period must be at most %d seconds, but got %d", h.maxChallenge, channelDef.Challenge), "") + return + } + logger = logger. WithKV("userWallet", incomingState.UserWallet). WithKV("asset", incomingState.Asset) @@ -54,8 +68,8 @@ func (h *Handler) RequestCreation(c *rpc.Context) { c.Fail(rpc.Errorf( "asset %s is not supported on blockchain %d with token address %s", incomingState.Asset, - incomingState.EscrowLedger.BlockchainID, - incomingState.EscrowLedger.TokenAddress), "") + incomingState.HomeLedger.BlockchainID, + incomingState.HomeLedger.TokenAddress), "") return } @@ -65,13 +79,34 @@ func (h *Handler) RequestCreation(c *rpc.Context) { return } + applicationID := rpc.GetApplicationID(c) + var nodeSig string err = h.useStoreInTx(func(tx Store) error { + // Gate the incoming transition through the action gateway before any state + // is signed, stored, or receiver-side state is issued. Mirrors SubmitState so + // gated actions (e.g. transfer_send) cannot bypass the 24h allowance by + // piggybacking on channel creation. + if err := h.actionGateway.AllowAction(tx, incomingState.UserWallet, incomingState.Transition.Type.GatedAction()); 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) } + // Reject if any home channel for this (wallet, asset) is not fully closed on-chain. + // This enforces one in-flight channel per asset and prevents epoch rebinding while + // a prior channel lifecycle is still pending settlement. + hasNonClosed, err := tx.HasNonClosedHomeChannel(incomingState.UserWallet, incomingState.Asset) + if err != nil { + return rpc.Errorf("failed to check channel lifecycle: %v", err) + } + if hasNonClosed { + return rpc.Errorf("existing channel is not yet closed on-chain; wait for settlement before opening a new channel") + } + // Check if channel already exists currentState, err := tx.GetLastUserState(incomingState.UserWallet, incomingState.Asset, false) if err != nil { @@ -101,22 +136,21 @@ func (h *Handler) RequestCreation(c *rpc.Context) { return rpc.Errorf("incoming state home_channel_id is invalid") } - if currentState.HomeChannelID != nil { - isFinal := currentState.IsFinal() - if !isFinal { - return rpc.Errorf("channel is already initialized") - } - if isFinal && strings.EqualFold(*incomingState.HomeChannelID, *currentState.HomeChannelID) { - return rpc.Errorf("cannot use same home channel id") - } + // Reject reuse of an already-known channel ID. Enforced at the handler rather than + // relying on the channels.channel_id primary key, so the invariant holds even when + // the prior state has no HomeChannelID (e.g., after a ChallengeRescue squash). + existingChannel, err := tx.GetChannelByID(homeChannelID) + if err != nil { + return rpc.Errorf("failed to look up channel by computed id: %v", err) } - - if channelDef.Nonce == 0 { - return rpc.Errorf("nonce must be non-zero") + if existingChannel != nil { + return rpc.Errorf("cannot use same home channel id") } - if channelDef.Challenge < h.minChallenge { - return rpc.Errorf("challenge period must be at least %d seconds, but got %d", h.minChallenge, channelDef.Challenge) + + if currentState.HomeChannelID != nil && !currentState.IsFinal() { + return rpc.Errorf("channel is already initialized") } + logger.Debug("processing channel creation request", "incomingVersion", incomingState.Version) if err := h.stateAdvancer.ValidateAdvancement(*currentState, incomingState); err != nil { @@ -178,6 +212,11 @@ func (h *Handler) RequestCreation(c *rpc.Context) { nodeSig = _nodeSig.String() incomingState.NodeSig = &nodeSig + // Store the pending state + if err := tx.StoreUserState(incomingState, applicationID); err != nil { + return rpc.Errorf("failed to store state: %v", err) + } + incomingTransition := incomingState.Transition if incomingTransition.Type != core.TransitionTypeAcknowledgement { @@ -195,7 +234,7 @@ func (h *Handler) RequestCreation(c *rpc.Context) { // We return Node's signature, the user is expected to submit this on blockchain. case core.TransitionTypeTransferSend: - newReceiverState, err := h.issueTransferReceiverState(ctx, tx, incomingState) + newReceiverState, err := h.issueTransferReceiverState(ctx, tx, incomingState, applicationID) if err != nil { return rpc.Errorf("failed to issue receiver state: %v", err) } @@ -207,7 +246,7 @@ func (h *Handler) RequestCreation(c *rpc.Context) { return rpc.Errorf("transition '%s' is not supported by this endpoint", incomingTransition.Type.String()) } - if err := tx.RecordTransaction(*transaction); err != nil { + if err := tx.RecordTransaction(*transaction, applicationID); err != nil { return rpc.Errorf("failed to record transaction") } @@ -219,10 +258,6 @@ func (h *Handler) RequestCreation(c *rpc.Context) { "asset", transaction.Asset, "amount", transaction.Amount.String()) } - // Store the pending state - if err := tx.StoreUserState(incomingState); err != nil { - return rpc.Errorf("failed to store state: %v", err) - } logger.Info("channel creation request processed", "homeChannelID", homeChannelID, diff --git a/nitronode/api/channel_v1/request_creation_test.go b/nitronode/api/channel_v1/request_creation_test.go new file mode 100644 index 000000000..0fd23fbab --- /dev/null +++ b/nitronode/api/channel_v1/request_creation_test.go @@ -0,0 +1,1112 @@ +package channel_v1 + +import ( + "context" + "errors" + "strconv" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func TestRequestCreation_Success(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) // 1 hour + maxChallenge := uint32(604800) // 7 days + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + err := handler(mockTxStore) + if err != nil { + return err + } + return nil + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + maxChallenge: maxChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + // Test data - derive userWallet from a user signer key + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + nonce := uint64(12345) + challenge := uint32(86400) + depositAmount := decimal.NewFromInt(1000) + + // Create void state (starting point) + voidState := core.NewVoidState(asset, userWallet) + + // Create next state from void + initialState := voidState.NextState() + + channelDef := core.ChannelDefinition{ + Nonce: nonce, + Challenge: challenge, + ApprovedSigValidators: "0x03", + } + _, err := initialState.ApplyChannelCreation(channelDef, blockchainID, tokenAddress, nodeAddress) + require.NoError(t, err) + + // Apply the home deposit transition to update balances + _, err = initialState.ApplyHomeDepositTransition(depositAmount) + require.NoError(t, err) + + // Set up mock for PackState (called during signing) + mockAssetStore.On("GetTokenDecimals", blockchainID, tokenAddress).Return(uint8(6), nil) + + // Sign the initial state with user's wallet signer (adds 0x01 prefix) + packedState, err := core.PackState(*initialState, mockAssetStore) + require.NoError(t, err) + userSig, err := userWalletSigner.Sign(packedState) + require.NoError(t, err) + userSigStr := userSig.String() + initialState.UserSig = &userSigStr + + // Mock expectations for handler + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil).Once() + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("HasNonClosedHomeChannel", userWallet, asset).Return(false, nil).Once() + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil).Once() + mockTxStore.On("GetChannelByID", mock.AnythingOfType("string")).Return((*core.Channel)(nil), nil).Once() + mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) + mockTxStore.On("CreateChannel", mock.MatchedBy(func(channel core.Channel) bool { + return channel.UserWallet == userWallet && + channel.Type == core.ChannelTypeHome && + channel.BlockchainID == blockchainID && + channel.TokenAddress == tokenAddress && + channel.Nonce == nonce && + channel.ChallengeDuration == challenge && + channel.Status == core.ChannelStatusVoid && + channel.StateVersion == 0 + })).Return(nil).Once() + mockTxStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + // For home_deposit: fromAccount is homeChannelID, toAccount is userWallet + return tx.TxType == core.TransactionTypeHomeDeposit && + tx.ToAccount == userWallet && + tx.FromAccount != "" // homeChannelID will be set by handler + }), mock.Anything).Return(nil).Once() + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == userWallet && + state.Asset == asset && + state.Version == 1 && + state.Epoch == 0 && + state.NodeSig != nil && + state.HomeChannelID != nil + }), mock.Anything).Return(nil).Once() + + // Create RPC request + rpcState := toRPCState(*initialState) + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpcState, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + // Execute + handler.RequestCreation(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Check for errors first + if respErr := ctx.Response.Error(); respErr != nil { + t.Fatalf("Unexpected error response: %v", respErr) + } + + assert.Equal(t, rpc.ChannelsV1RequestCreationMethod.String(), ctx.Response.Method) + assert.NotNil(t, ctx.Response.Payload) + + // Verify response contains signature + var response rpc.ChannelsV1RequestCreationResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.NotEmpty(t, response.Signature) + + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + + // Verify all mocks were called + mockMemoryStore.AssertExpectations(t) + mockAssetStore.AssertExpectations(t) + mockTxStore.AssertExpectations(t) +} + +func TestRequestCreation_Acknowledgement_Success(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) // 1 hour + maxChallenge := uint32(604800) // 7 days + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + err := handler(mockTxStore) + if err != nil { + return err + } + return nil + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + maxChallenge: maxChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + // Test data - derive userWallet from a user signer key + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + nonce := uint64(12345) + challenge := uint32(86400) + + // Create void state (starting point) + voidState := core.NewVoidState(asset, userWallet) + + // Create next state from void + initialState := voidState.NextState() + + channelDef := core.ChannelDefinition{ + Nonce: nonce, + Challenge: challenge, + ApprovedSigValidators: "0x03", + } + _, err := initialState.ApplyChannelCreation(channelDef, blockchainID, tokenAddress, nodeAddress) + require.NoError(t, err) + + // Apply acknowledgement transition (channel creation with no deposit) + _, err = initialState.ApplyAcknowledgementTransition() + require.NoError(t, err) + + // Set up mock for PackState (called during signing) + mockAssetStore.On("GetTokenDecimals", blockchainID, tokenAddress).Return(uint8(6), nil) + + // Sign the initial state with user's wallet signer (adds 0x01 prefix) + packedState, err := core.PackState(*initialState, mockAssetStore) + require.NoError(t, err) + userSig, err := userWalletSigner.Sign(packedState) + require.NoError(t, err) + userSigStr := userSig.String() + initialState.UserSig = &userSigStr + + // Mock expectations for handler + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil).Once() + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("HasNonClosedHomeChannel", userWallet, asset).Return(false, nil).Once() + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil).Once() + mockTxStore.On("GetChannelByID", mock.AnythingOfType("string")).Return((*core.Channel)(nil), nil).Once() + mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) + mockTxStore.On("CreateChannel", mock.MatchedBy(func(channel core.Channel) bool { + return channel.UserWallet == userWallet && + channel.Type == core.ChannelTypeHome && + channel.BlockchainID == blockchainID && + channel.TokenAddress == tokenAddress && + channel.Nonce == nonce && + channel.ChallengeDuration == challenge && + channel.Status == core.ChannelStatusVoid && + channel.StateVersion == 0 + })).Return(nil).Once() + // For acknowledgement: no RecordTransaction call expected, only StoreUserState + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == userWallet && + state.Asset == asset && + state.Version == 1 && + state.Epoch == 0 && + state.NodeSig != nil && + state.HomeChannelID != nil && + state.Transition.Type == core.TransitionTypeAcknowledgement + }), mock.Anything).Return(nil).Once() + + // Create RPC request + rpcState := toRPCState(*initialState) + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpcState, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + // Execute + handler.RequestCreation(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Check for errors first + if respErr := ctx.Response.Error(); respErr != nil { + t.Fatalf("Unexpected error response: %v", respErr) + } + + assert.Equal(t, rpc.ChannelsV1RequestCreationMethod.String(), ctx.Response.Method) + assert.NotNil(t, ctx.Response.Payload) + + // Verify response contains signature + var response rpc.ChannelsV1RequestCreationResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.NotEmpty(t, response.Signature) + + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + + // Verify all mocks were called - notably RecordTransaction should NOT have been called + mockMemoryStore.AssertExpectations(t) + mockAssetStore.AssertExpectations(t) + mockTxStore.AssertExpectations(t) + mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) +} + +func TestRequestCreation_InvalidChallenge(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) // 1 hour + maxChallenge := uint32(604800) // 7 days + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + maxChallenge: maxChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + // Test data + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + tokenAddress := "0xToken" + nonce := uint64(12345) + lowChallenge := uint32(1800) // 30 minutes - below minimum + + // Calculate home channel ID + homeChannelID, err := core.GetHomeChannelID( + nodeAddress, + userWallet, + asset, + nonce, + lowChallenge, + "0x03", + ) + require.NoError(t, err) + + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, uint64(1)).Return(true, nil).Once() + + // Create RPC request with challenge below minimum + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpc.StateV1{ + ID: core.GetStateID(userWallet, asset, 1, 1), + UserWallet: userWallet, + Asset: asset, + Epoch: "1", + Version: "1", + HomeChannelID: &homeChannelID, + Transition: rpc.TransitionV1{ + Amount: "0", + }, + HomeLedger: rpc.LedgerV1{ + TokenAddress: tokenAddress, + BlockchainID: "1", + UserBalance: "0", + UserNetFlow: "0", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: lowChallenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + // Execute + handler.RequestCreation(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Verify response contains error + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "challenge") + + // Verify all mocks were called + mockTxStore.AssertExpectations(t) +} + +func TestRequestCreation_ChallengeTooHigh(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) // 1 hour + maxChallenge := uint32(604800) // 7 days + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + maxChallenge: maxChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + // Test data + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + tokenAddress := "0xToken" + nonce := uint64(12345) + highChallenge := uint32(1209600) // 14 days - above maximum + + // Calculate home channel ID + homeChannelID, err := core.GetHomeChannelID( + nodeAddress, + userWallet, + asset, + nonce, + highChallenge, + "0x03", + ) + require.NoError(t, err) + + // Create RPC request with challenge above maximum + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpc.StateV1{ + ID: core.GetStateID(userWallet, asset, 1, 1), + UserWallet: userWallet, + Asset: asset, + Epoch: "1", + Version: "1", + HomeChannelID: &homeChannelID, + Transition: rpc.TransitionV1{ + Amount: "0", + }, + HomeLedger: rpc.LedgerV1{ + TokenAddress: tokenAddress, + BlockchainID: "1", + UserBalance: "0", + UserNetFlow: "0", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: highChallenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + // Execute + handler.RequestCreation(ctx) + + // Assert + assert.NotNil(t, ctx.Response) + + // Verify response contains error about challenge being too high + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "challenge") + assert.Contains(t, err.Error(), "at most") +} + +// TestRequestCreation_NonClosedChannelRejection verifies that opening a new channel is +// rejected while a prior channel is still in progress (Closing, Open, or Challenged), +// preventing epoch rebinding by ensuring only one channel lifecycle runs at a time. +func TestRequestCreation_NonClosedChannelRejection(t *testing.T) { + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: uint32(3600), + maxChallenge: uint32(604800), + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + nonce := uint64(99) + challenge := uint32(86400) + + homeChannelID, err := core.GetHomeChannelID(nodeAddress, userWallet, asset, nonce, challenge, "0x03") + require.NoError(t, err) + + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + + // Gate fires: a non-closed channel exists (e.g., the channel is Closing after off-chain Finalize). + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("HasNonClosedHomeChannel", userWallet, asset).Return(true, nil).Once() + + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpc.StateV1{ + ID: core.GetStateID(userWallet, asset, 1, 1), + UserWallet: userWallet, + Asset: asset, + Epoch: "1", + Version: "1", + HomeChannelID: &homeChannelID, + Transition: rpc.TransitionV1{Amount: "100"}, + HomeLedger: rpc.LedgerV1{ + TokenAddress: tokenAddress, + BlockchainID: "1", + UserBalance: "100", + UserNetFlow: "100", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + handler.RequestCreation(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.Error(t, respErr) + assert.Contains(t, respErr.Error(), "not yet closed") + + mockTxStore.AssertExpectations(t) + // GetLastUserState, CreateChannel, StoreUserState must NOT be called. + mockTxStore.AssertNotCalled(t, "GetLastUserState", mock.Anything, mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "CreateChannel", mock.Anything) + mockTxStore.AssertNotCalled(t, "StoreUserState", mock.Anything, mock.Anything) +} + +// TestRequestCreation_TransferSend_Success verifies that a TransferSend transition +// on initial channel creation passes the action gateway and produces both +// sender-side and receiver-side state effects. Covers the happy path opposite +// TestRequestCreation_ActionGatewayRejection. +func TestRequestCreation_TransferSend_Success(t *testing.T) { + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: uint32(3600), + maxChallenge: uint32(604800), + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + senderWallet := userSigner.PublicKey().Address().String() + receiverWallet := "0x0987654321098765432109876543210987654321" + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + nonce := uint64(12345) + challenge := uint32(86400) + transferAmount := decimal.NewFromInt(100) + + // Sender's prior state: positive offchain balance from a prior transfer_receive, + // no active home channel. This is the bypass scenario MF2-L01 closes. + currentSenderState := core.State{ + ID: core.GetStateID(senderWallet, asset, 0, 1), + Asset: asset, + UserWallet: senderWallet, + Epoch: 0, + Version: 1, + HomeChannelID: nil, + HomeLedger: core.Ledger{ + TokenAddress: tokenAddress, + BlockchainID: blockchainID, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.NewFromInt(500), + }, + Transition: core.Transition{Type: core.TransitionTypeTransferReceive}, + } + + // Build proposed sender state: NextState + ApplyChannelCreation + ApplyTransferSend. + incomingSenderState := currentSenderState.NextState() + channelDef := core.ChannelDefinition{ + Nonce: nonce, + Challenge: challenge, + ApprovedSigValidators: "0x03", + } + _, err := incomingSenderState.ApplyChannelCreation(channelDef, blockchainID, tokenAddress, nodeAddress) + require.NoError(t, err) + _, err = incomingSenderState.ApplyTransferSendTransition(receiverWallet, transferAmount) + require.NoError(t, err) + + mockAssetStore.On("GetTokenDecimals", blockchainID, tokenAddress).Return(uint8(6), nil) + packedState, err := core.PackState(*incomingSenderState, mockAssetStore) + require.NoError(t, err) + userSig, err := userWalletSigner.Sign(packedState) + require.NoError(t, err) + userSigStr := userSig.String() + incomingSenderState.UserSig = &userSigStr + + // Handler-side mocks. + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil).Once() + mockTxStore.On("LockUserState", senderWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("HasNonClosedHomeChannel", senderWallet, asset).Return(false, nil).Once() + mockTxStore.On("GetLastUserState", senderWallet, asset, false).Return(currentSenderState, nil).Once() + mockTxStore.On("GetChannelByID", mock.AnythingOfType("string")).Return((*core.Channel)(nil), nil).Once() + mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) + mockTxStore.On("CreateChannel", mock.MatchedBy(func(channel core.Channel) bool { + return channel.UserWallet == senderWallet && + channel.Type == core.ChannelTypeHome && + channel.BlockchainID == blockchainID && + channel.TokenAddress == tokenAddress && + channel.Nonce == nonce && + channel.ChallengeDuration == challenge + })).Return(nil).Once() + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == senderWallet && + state.Asset == asset && + state.Transition.Type == core.TransitionTypeTransferSend && + state.NodeSig != nil && + state.HomeChannelID != nil + }), mock.Anything).Return(nil).Once() + + // Receiver-side: fresh user, no prior state, no home channel. + mockTxStore.On("LockUserState", receiverWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("GetLastUserState", receiverWallet, asset, false).Return(nil, nil).Once() + mockTxStore.On("EnsureNoOngoingEscrowOperation", receiverWallet, asset).Return(nil).Once() + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == receiverWallet && + state.Asset == asset && + state.Transition.Type == core.TransitionTypeTransferReceive + }), mock.Anything).Return(nil).Once() + + mockTxStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + return tx.TxType == core.TransactionTypeTransfer && + tx.Amount.Equal(transferAmount) && + tx.FromAccount == senderWallet && + tx.ToAccount == receiverWallet + }), mock.Anything).Return(nil).Once() + + rpcState := toRPCState(*incomingSenderState) + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpcState, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + handler.RequestCreation(ctx) + + require.NotNil(t, ctx.Response) + if respErr := ctx.Response.Error(); respErr != nil { + t.Fatalf("Unexpected error response: %v", respErr) + } + var response rpc.ChannelsV1RequestCreationResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.NotEmpty(t, response.Signature) + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + + mockMemoryStore.AssertExpectations(t) + mockAssetStore.AssertExpectations(t) + mockTxStore.AssertExpectations(t) +} + +// TestRequestCreation_ActionGatewayRejection verifies that an exhausted gated +// action (e.g. transfer_send) is rejected at the gateway before any state is +// signed, stored, or receiver-side state is issued. Mirrors the SubmitState +// gate so users cannot bypass the 24h allowance via initial channel creation. +func TestRequestCreation_ActionGatewayRejection(t *testing.T) { + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: uint32(3600), + maxChallenge: uint32(604800), + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{Err: errors.New("transfer allowance exhausted")}, + } + + userSigner := NewMockSigner() + userWallet := userSigner.PublicKey().Address().String() + receiverWallet := "0x0987654321098765432109876543210987654321" + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + nonce := uint64(77) + challenge := uint32(86400) + + homeChannelID, err := core.GetHomeChannelID(nodeAddress, userWallet, asset, nonce, challenge, "0x03") + require.NoError(t, err) + + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpc.StateV1{ + ID: core.GetStateID(userWallet, asset, 1, 1), + UserWallet: userWallet, + Asset: asset, + Epoch: "1", + Version: "1", + HomeChannelID: &homeChannelID, + Transition: rpc.TransitionV1{ + Type: core.TransitionTypeTransferSend, + AccountID: receiverWallet, + Amount: "100", + }, + HomeLedger: rpc.LedgerV1{ + TokenAddress: tokenAddress, + BlockchainID: "1", + UserBalance: "0", + UserNetFlow: "0", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + handler.RequestCreation(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.Error(t, respErr) + assert.Contains(t, respErr.Error(), "transfer allowance exhausted") + + // Gate fires before any state effect: nothing should be locked, stored, or recorded. + mockTxStore.AssertNotCalled(t, "LockUserState", mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "HasNonClosedHomeChannel", mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "GetLastUserState", mock.Anything, mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "CreateChannel", mock.Anything) + mockTxStore.AssertNotCalled(t, "StoreUserState", mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) + mockMemoryStore.AssertExpectations(t) +} + +// TestRequestCreation_RejectReusedHomeChannelID covers the case where the user's last +// state has no HomeChannelID (e.g., it is a ChallengeRescue squash on a fresh epoch), +// but the computed channel ID matches a previously stored channel record. The handler +// must reject the request explicitly rather than deferring the duplicate detection to +// the channels.channel_id primary key in CreateChannel. +func TestRequestCreation_RejectReusedHomeChannelID(t *testing.T) { + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: uint32(3600), + maxChallenge: uint32(604800), + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + nonce := uint64(7) + challenge := uint32(86400) + + homeChannelID, err := core.GetHomeChannelID(nodeAddress, userWallet, asset, nonce, challenge, "0x03") + require.NoError(t, err) + + // Simulate the post-rescue ledger head: a state whose HomeChannelID is nil because + // it was issued by issueChallengeRescue() after the prior home channel was closed. + rescueHead := core.State{ + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 1, + HomeChannelID: nil, + } + + // Channel record from the closed epoch still lives in the channels table. + priorChannel := &core.Channel{ + ChannelID: homeChannelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + BlockchainID: blockchainID, + TokenAddress: tokenAddress, + Status: core.ChannelStatusClosed, + Nonce: nonce, + } + + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("HasNonClosedHomeChannel", userWallet, asset).Return(false, nil).Once() + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(rescueHead, nil).Once() + mockTxStore.On("GetChannelByID", mock.AnythingOfType("string")).Return(priorChannel, nil).Once() + + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpc.StateV1{ + ID: core.GetStateID(userWallet, asset, 2, 1), + UserWallet: userWallet, + Asset: asset, + Epoch: "2", + Version: "1", + HomeChannelID: &homeChannelID, + Transition: rpc.TransitionV1{Amount: "0"}, + HomeLedger: rpc.LedgerV1{ + TokenAddress: tokenAddress, + BlockchainID: "1", + UserBalance: "0", + UserNetFlow: "0", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(nonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + handler.RequestCreation(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.Error(t, respErr) + assert.Contains(t, respErr.Error(), "cannot use same home channel id") + + mockTxStore.AssertExpectations(t) + mockTxStore.AssertNotCalled(t, "CreateChannel", mock.Anything) + mockTxStore.AssertNotCalled(t, "StoreUserState", mock.Anything, mock.Anything) +} + +// TestRequestCreation_ChannelAlreadyInitialized covers the guard that rejects a creation +// request when the user's prior state still has a non-nil, non-final HomeChannelID and +// the computed channel ID is not yet stored. This is the in-flight-channel case: the +// previous lifecycle has not been finalized off-chain, so a new creation must not start. +func TestRequestCreation_ChannelAlreadyInitialized(t *testing.T) { + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: uint32(3600), + maxChallenge: uint32(604800), + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + tokenAddress := "0xTokenAddress" + blockchainID := uint64(1) + priorNonce := uint64(7) + newNonce := uint64(8) + challenge := uint32(86400) + + priorHomeChannelID, err := core.GetHomeChannelID(nodeAddress, userWallet, asset, priorNonce, challenge, "0x03") + require.NoError(t, err) + newHomeChannelID, err := core.GetHomeChannelID(nodeAddress, userWallet, asset, newNonce, challenge, "0x03") + require.NoError(t, err) + + // Prior state: in-flight channel — HomeChannelID set, transition is not Finalize. + priorState := core.State{ + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &priorHomeChannelID, + Transition: core.Transition{Type: core.TransitionTypeHomeDeposit}, + } + + mockMemoryStore.On("IsAssetSupported", asset, tokenAddress, blockchainID).Return(true, nil).Once() + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil).Once() + mockTxStore.On("HasNonClosedHomeChannel", userWallet, asset).Return(false, nil).Once() + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(priorState, nil).Once() + // New nonce → fresh channel ID, no row yet in channels. + mockTxStore.On("GetChannelByID", mock.AnythingOfType("string")).Return((*core.Channel)(nil), nil).Once() + + reqPayload := rpc.ChannelsV1RequestCreationRequest{ + State: rpc.StateV1{ + ID: core.GetStateID(userWallet, asset, 2, 1), + UserWallet: userWallet, + Asset: asset, + Epoch: "2", + Version: "1", + HomeChannelID: &newHomeChannelID, + Transition: rpc.TransitionV1{Amount: "0"}, + HomeLedger: rpc.LedgerV1{ + TokenAddress: tokenAddress, + BlockchainID: "1", + UserBalance: "0", + UserNetFlow: "0", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + ChannelDefinition: rpc.ChannelDefinitionV1{ + Nonce: strconv.FormatUint(newNonce, 10), + Challenge: challenge, + ApprovedSigValidators: "0x03", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{ + RequestID: 1, + Method: rpc.ChannelsV1RequestCreationMethod.String(), + Payload: payload, + }, + } + + handler.RequestCreation(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.Error(t, respErr) + assert.Contains(t, respErr.Error(), "channel is already initialized") + + mockTxStore.AssertExpectations(t) + mockTxStore.AssertNotCalled(t, "CreateChannel", mock.Anything) + mockTxStore.AssertNotCalled(t, "StoreUserState", mock.Anything, mock.Anything) +} diff --git a/nitronode/api/channel_v1/submit_session_key_state.go b/nitronode/api/channel_v1/submit_session_key_state.go new file mode 100644 index 000000000..5bd7db3f3 --- /dev/null +++ b/nitronode/api/channel_v1/submit_session_key_state.go @@ -0,0 +1,172 @@ +package channel_v1 + +import ( + "errors" + "strings" + "time" + + "github.com/layer-3/nitrolite/nitronode/store/database" + "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. +func (h *Handler) SubmitSessionKeyState(c *rpc.Context) { + ctx := c.Context + logger := log.FromContext(ctx) + + var reqPayload rpc.ChannelsV1SubmitSessionKeyStateRequest + if err := c.Request.Payload.Translate(&reqPayload); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + logger.Debug("processing channel session key state submission", + "userAddress", reqPayload.State.UserAddress, + "sessionKey", reqPayload.State.SessionKey, + "version", reqPayload.State.Version) + + // Convert RPC type to core type + coreState, err := unmapChannelSessionKeyStateV1(&reqPayload.State) + if err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") + return + } + + // Validate required fields + coreState.UserAddress, err = core.NormalizeHexAddress(coreState.UserAddress) + if err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: invalid user_address: %v", err), "") + return + } + + coreState.SessionKey, err = core.NormalizeHexAddress(coreState.SessionKey) + if err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: invalid session_key: %v", err), "") + return + } + + if strings.EqualFold(coreState.UserAddress, coreState.SessionKey) { + c.Fail(rpc.Errorf("invalid_session_key_state: session_key must differ from user_address"), "") + return + } + + if coreState.Version == 0 { + c.Fail(rpc.Errorf("invalid_session_key_state: version must be greater than 0"), "") + return + } + // Past expires_at is permitted as a revocation signal. The auth path filters + // expires_at > now so a past timestamp deactivates the key immediately while keeping + // the same monotonic version sequence (a later submit with a future expires_at can + // re-activate the key). A negative unix timestamp is rejected because the + // metadata-hash packer casts int64 -> uint64 (wraps to a huge future value), which + // would cause the user-signed payload to disagree with the value persisted in the + // database — defense-in-depth even though the DB filter is the source of truth. + if coreState.ExpiresAt.Unix() < 0 { + c.Fail(rpc.Errorf("invalid_session_key_state: expires_at must be non-negative"), "") + return + } + if len(coreState.Assets) > h.maxSessionKeyIDs { + c.Fail(rpc.Errorf("invalid_session_key_state: assets array exceeds maximum length of %d", h.maxSessionKeyIDs), "") + return + } + if coreState.UserSig == "" { + c.Fail(rpc.Errorf("invalid_session_key_state: user_sig is required"), "") + return + } + if coreState.SessionKeySig == "" { + c.Fail(rpc.Errorf("invalid_session_key_state: session_key_sig is required"), "") + return + } + + // Validate both signatures: wallet's user_sig and session-key holder's session_key_sig. + if err := core.ValidateChannelSessionKeyStateV1(coreState); err != nil { + c.Fail(rpc.Errorf("invalid_session_key_state: %v", err), "") + return + } + + // Validate version and store the session key state + now := time.Now() + err = h.useStoreInTx(func(tx Store) error { + // Lock the (user, session_key, channel) pointer row for the duration of the tx so that + // concurrent submits for the same (user, session_key) serialize cleanly and report a + // proper "expected version" error rather than racing on the history UNIQUE constraint. + latestVersion, latestExpiresAt, err := tx.LockSessionKeyState(coreState.UserAddress, coreState.SessionKey, database.SessionKeyKindChannel) + if err != nil { + if errors.Is(err, database.ErrSessionKeyNotAllowed) { + logger.Warn("session key registration collision", + "userAddress", coreState.UserAddress, + "sessionKey", coreState.SessionKey, + "kind", database.SessionKeyKindChannel) + return rpc.Errorf("invalid_session_key_state: session_key not allowed") + } + return rpc.Errorf("failed to lock session key state: %v", err) + } + + // Enforce the per-user cap whenever this submit transitions the slot from inactive + // to active — i.e. a brand-new key (latestVersion == 0) or a reactivation where the + // previous latest state was already past its expires_at. A rotation/update against a + // still-active key is not counted again so legitimate rotation is never blocked, and + // a revoke submit (submitted expires_at <= now) decreases the active count so it is + // not subject to the cap either. + // + // Without the reactivation check a user at the cap can revoke key A, register fresh + // key B into the freed slot, then re-submit key A with a future expires_at — the + // `latestVersion > 0` branch would skip the cap check and leave the user above the + // cap. + // + // TODO(MF-H01-followup): the row lock above only serializes submits for the same + // (user, session_key, kind), so two concurrent submits registering *different* new keys + // for the same user can both observe the same count and both pass the check, ending up + // at most maxSessionKeysPerUser + (concurrent new-key writers - 1) keys. The cap is a + // soft DOS bound, not a hard quota — a small over-shoot under genuine concurrency is + // acceptable. If a hard quota is ever required, take a per-user advisory lock here + // (pg_advisory_xact_lock(hashtext(user_address))) before counting. + prevActive := latestVersion > 0 && latestExpiresAt.After(now) + submittedActive := coreState.ExpiresAt.After(now) + if !prevActive && submittedActive && h.maxSessionKeysPerUser > 0 { + count, err := tx.CountSessionKeysForUser(coreState.UserAddress) + if err != nil { + return rpc.Errorf("failed to count session keys for user: %v", err) + } + if count >= uint32(h.maxSessionKeysPerUser) { + return rpc.Errorf("invalid_session_key_state: user has reached the session key limit of %d", h.maxSessionKeysPerUser) + } + } + + if coreState.Version != latestVersion+1 { + return rpc.Errorf("invalid_session_key_state: expected version %d, got %d", latestVersion+1, coreState.Version) + } + + return tx.StoreChannelSessionKeyState(coreState) + }) + + if err != nil { + logger.Error("failed to store channel session key state", "error", err) + c.Fail(err, "failed to store channel session key state") + return + } + + resp := rpc.ChannelsV1SubmitSessionKeyStateResponse{} + + payload, err := rpc.NewPayload(resp) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) + if !coreState.ExpiresAt.After(now) { + logger.Info("channel session key revoked", + "userAddress", coreState.UserAddress, + "sessionKey", coreState.SessionKey, + "version", coreState.Version, + "expiresAt", coreState.ExpiresAt) + return + } + logger.Info("successfully stored channel session key state", + "userAddress", coreState.UserAddress, + "sessionKey", coreState.SessionKey, + "version", coreState.Version) +} diff --git a/nitronode/api/channel_v1/submit_session_key_state_test.go b/nitronode/api/channel_v1/submit_session_key_state_test.go new file mode 100644 index 000000000..80ebcf0ca --- /dev/null +++ b/nitronode/api/channel_v1/submit_session_key_state_test.go @@ -0,0 +1,762 @@ +package channel_v1 + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/nitronode/store/database" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" +) + +// buildSignedChannelSessionKeyStateReq creates a properly signed ChannelsV1SubmitSessionKeyState request. +// Both signer (wallet UserSig) and keySigner (SessionKeySig) sign over the same +// PackChannelKeyStateV1 payload. session_key is bound into the metadata hash, so a signature +// minted for one key cannot be replayed as ownership of another. Pass nil for keySigner to +// leave SessionKeySig empty for negative-path tests. +func buildSignedChannelSessionKeyStateReq(t *testing.T, userAddress, sessionKey string, version uint64, assets []string, expiresAt time.Time, signer, keySigner sign.Signer) rpc.ChannelsV1SubmitSessionKeyStateRequest { + t.Helper() + + if assets == nil { + assets = []string{} + } + + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(strings.ToLower(userAddress), version, assets, expiresAt.Unix()) + require.NoError(t, err) + + packed, err := core.PackChannelKeyStateV1(strings.ToLower(sessionKey), metadataHash) + require.NoError(t, err) + + sig, err := signer.Sign(packed) + require.NoError(t, err) + + state := rpc.ChannelSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKey, + Version: strconv.FormatUint(version, 10), + Assets: assets, + ExpiresAt: strconv.FormatInt(expiresAt.Unix(), 10), + UserSig: hexutil.Encode(sig), + } + + if keySigner != nil { + keySig, err := keySigner.Sign(packed) + require.NoError(t, err) + state.SessionKeySig = hexutil.Encode(keySig) + } + + return rpc.ChannelsV1SubmitSessionKeyStateRequest{State: state} +} + +func TestChannelSubmitSessionKeyState_Success(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + assets := []string{"USDC"} + + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, assets, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(0, time.Time{}, nil) + mockStore.On("StoreChannelSessionKeyState", mock.AnythingOfType("core.ChannelSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) + mockStore.AssertExpectations(t) +} + +func TestChannelSubmitSessionKeyState_AssetsExceedsMax(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 2, + } + + // 3 assets exceeds max of 2 + reqPayload := rpc.ChannelsV1SubmitSessionKeyStateRequest{ + State: rpc.ChannelSessionKeyStateV1{ + UserAddress: "0x1111111111111111111111111111111111111111", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + Assets: []string{"USDC", "ETH", "BTC"}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "assets array exceeds maximum length of 2") +} + +func TestChannelSubmitSessionKeyState_AtMaxLimit(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 2, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // Exactly at max (2) should pass validation + assets := []string{"USDC", "ETH"} + + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, assets, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(0, time.Time{}, nil) + mockStore.On("StoreChannelSessionKeyState", mock.AnythingOfType("core.ChannelSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} + +func TestChannelSubmitSessionKeyState_InvalidUserAddress(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + reqPayload := rpc.ChannelsV1SubmitSessionKeyStateRequest{ + State: rpc.ChannelSessionKeyStateV1{ + UserAddress: "not-an-address", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + Assets: []string{}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "invalid user_address") +} + +func TestChannelSubmitSessionKeyState_RevokeWithPastExpiresAt(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + // expires_at in the past expresses a revoke: the same monotonic version sequence + // is preserved, the auth path filters expires_at > now so the key is deactivated. + expiresAt := time.Now().Add(-time.Hour).Truncate(time.Second) + assets := []string{"USDC"} + + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, assets, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(0, time.Time{}, nil) + mockStore.On("StoreChannelSessionKeyState", mock.AnythingOfType("core.ChannelSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} + +// Covers the typical revocation path: an active key (latestVersion > 0, prev expires_at in +// the future) is deactivated by submitting version+1 with a past expires_at. The per-user +// cap check is short-circuited because the previous state was already active (revoke +// decreases the active count), so CountSessionKeysForUser must not be called. +func TestChannelSubmitSessionKeyState_RevokeExistingActiveKey(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 5, + } + + expiresAt := time.Now().Add(-time.Hour).Truncate(time.Second) + assets := []string{"USDC"} + + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 2, assets, expiresAt, userSigner, sessionKeySigner) + + prevActiveExpiresAt := time.Now().Add(24 * time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(1, prevActiveExpiresAt, nil) + mockStore.On("StoreChannelSessionKeyState", mock.AnythingOfType("core.ChannelSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "CountSessionKeysForUser", mock.Anything) +} + +// Covers the re-activation path: after a revoke (latestVersion > 0, prev expires_at in the +// past), submitting version+1 with a future expires_at re-activates the slot — i.e. the +// active count goes from N-1 back to N. Because the previous latest state was inactive, the +// per-user cap MUST be re-checked here so a user at the cap cannot revoke→register-new→ +// reactivate to exceed it. +func TestChannelSubmitSessionKeyState_ReactivateAfterRevoke_BelowCapAllowed(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 5, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + assets := []string{"USDC"} + + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 3, assets, expiresAt, userSigner, sessionKeySigner) + + prevRevokedExpiresAt := time.Now().Add(-time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(2, prevRevokedExpiresAt, nil) + mockStore.On("CountSessionKeysForUser", userAddress).Return(4, nil) + mockStore.On("StoreChannelSessionKeyState", mock.AnythingOfType("core.ChannelSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} + +// Reactivating a revoked key when the user is already at the per-user cap must be rejected. +// Without this gate a user at the cap can revoke key A, register fresh key B into the freed +// slot, then re-submit key A with a future expires_at and end up above the cap. +func TestChannelSubmitSessionKeyState_ReactivateAfterRevoke_AtCapRejected(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 3, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + assets := []string{"USDC"} + + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 3, assets, expiresAt, userSigner, sessionKeySigner) + + prevRevokedExpiresAt := time.Now().Add(-time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(2, prevRevokedExpiresAt, nil) + mockStore.On("CountSessionKeysForUser", userAddress).Return(3, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session key limit of 3") + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "StoreChannelSessionKeyState", mock.Anything) +} + +func TestChannelSubmitSessionKeyState_RejectsNegativeExpiresAt(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + reqPayload := rpc.ChannelsV1SubmitSessionKeyStateRequest{ + State: rpc.ChannelSessionKeyStateV1{ + UserAddress: "0x1111111111111111111111111111111111111111", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + Assets: []string{}, + ExpiresAt: "-1", + UserSig: "0xdeadbeef", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "expires_at must be non-negative") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestChannelSubmitSessionKeyState_MissingUserSig(t *testing.T) { + mockStore := new(MockStore) + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + reqPayload := rpc.ChannelsV1SubmitSessionKeyStateRequest{ + State: rpc.ChannelSessionKeyStateV1{ + UserAddress: "0x1111111111111111111111111111111111111111", + SessionKey: "0x3333333333333333333333333333333333333333", + Version: "1", + Assets: []string{}, + ExpiresAt: strconv.FormatInt(time.Now().Add(time.Hour).Unix(), 10), + UserSig: "", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "user_sig is required") +} + +func TestChannelSubmitSessionKeyState_VersionMismatch(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + // Submit version 3 when latest is 0 (expects 1) + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 3, []string{}, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(0, time.Time{}, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), fmt.Sprintf("expected version %d, got %d", 1, 3)) + mockStore.AssertExpectations(t) +} + +func TestChannelSubmitSessionKeyState_RejectsWhenAtUserCap(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 3, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, []string{"USDC"}, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(0, time.Time{}, nil) + mockStore.On("CountSessionKeysForUser", userAddress).Return(3, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session key limit of 3") + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "StoreChannelSessionKeyState", mock.Anything) +} + +func TestChannelSubmitSessionKeyState_AllowsUpdateForExistingKeyAtCap(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + maxSessionKeysPerUser: 3, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // Existing key at version 4: submit version 5. Cap must NOT block updates. + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 5, []string{"USDC"}, expiresAt, userSigner, sessionKeySigner) + + prevActiveExpiresAt := time.Now().Add(24 * time.Hour) + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel).Return(4, prevActiveExpiresAt, nil) + mockStore.On("StoreChannelSessionKeyState", mock.AnythingOfType("core.ChannelSessionKeyStateV1")).Return(nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + assert.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "CountSessionKeysForUser", mock.Anything) +} + +func TestChannelSubmitSessionKeyState_SignatureMismatch(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + differentSigner := NewMockSigner() // sign with a different key + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + // Sign with differentSigner but claim userAddress + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, []string{}, expiresAt, differentSigner, sessionKeySigner) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "does not match wallet") +} + +func TestChannelSubmitSessionKeyState_RejectsUserAddressEqualsSessionKey(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, userAddress, 1, []string{"USDC"}, expiresAt, userSigner, userSigner) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key must differ from user_address") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestChannelSubmitSessionKeyState_RejectsMissingSessionKeySig(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // keySigner=nil → SessionKeySig field stays empty. + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, []string{"USDC"}, expiresAt, userSigner, nil) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key_sig is required") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestChannelSubmitSessionKeyState_RejectsMismatchedSessionKeySig(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + otherSigner := NewMockSigner() + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + // SessionKeySig from a key that does not match the declared session_key. + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, []string{"USDC"}, expiresAt, userSigner, otherSigner) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key_sig does not match session_key") + mockStore.AssertNotCalled(t, "LockSessionKeyState", mock.Anything, mock.Anything, mock.Anything) +} + +func TestChannelSubmitSessionKeyState_RejectsForeignOwner(t *testing.T) { + mockStore := new(MockStore) + userSigner := NewMockSigner() + userAddress := strings.ToLower(userSigner.PublicKey().Address().String()) + sessionKeySigner := NewMockSigner() + sessionKeyAddress := strings.ToLower(sessionKeySigner.PublicKey().Address().String()) + + handler := &Handler{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 10, + } + + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + reqPayload := buildSignedChannelSessionKeyStateReq(t, userAddress, sessionKeyAddress, 1, []string{"USDC"}, expiresAt, userSigner, sessionKeySigner) + + mockStore.On("LockSessionKeyState", userAddress, sessionKeyAddress, database.SessionKeyKindChannel). + Return(0, time.Time{}, database.ErrSessionKeyNotAllowed) + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, rpc.ChannelsV1SubmitSessionKeyStateMethod.String(), payload), + } + + handler.SubmitSessionKeyState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "session_key not allowed") + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "StoreChannelSessionKeyState", mock.Anything) +} diff --git a/clearnode/api/channel_v1/submit_state.go b/nitronode/api/channel_v1/submit_state.go similarity index 79% rename from clearnode/api/channel_v1/submit_state.go rename to nitronode/api/channel_v1/submit_state.go index 2daf5867d..b5fa1e349 100644 --- a/clearnode/api/channel_v1/submit_state.go +++ b/nitronode/api/channel_v1/submit_state.go @@ -21,12 +21,21 @@ func (h *Handler) SubmitState(c *rpc.Context) { return } + var err error + reqPayload.State.UserWallet, err = core.NormalizeHexAddress(reqPayload.State.UserWallet) + if err != nil { + c.Fail(rpc.Errorf("invalid user_wallet: %v", err), "") + return + } + incomingState, err := toCoreState(reqPayload.State) if err != nil { c.Fail(err, "failed to parse state") return } + applicationID := rpc.GetApplicationID(c) + var nodeSig string incomingTransition := incomingState.Transition err = h.useStoreInTx(func(tx Store) error { @@ -40,12 +49,12 @@ func (h *Handler) SubmitState(c *rpc.Context) { return rpc.Errorf("failed to lock user state: %v", err) } - approvedSigValidators, userHasOpenChannel, err := tx.CheckOpenChannel(incomingState.UserWallet, incomingState.Asset) + approvedSigValidators, channelStatus, err := tx.CheckActiveChannel(incomingState.UserWallet, incomingState.Asset) if err != nil { - return rpc.Errorf("failed to check open channel: %v", err) + return rpc.Errorf("failed to check active channel: %v", err) } - if !userHasOpenChannel { - return rpc.Errorf("user has no open channel") + if channelStatus == nil { + return rpc.Errorf("user has no active channel") } logger.Debug("processing incoming state", @@ -61,6 +70,15 @@ func (h *Handler) SubmitState(c *rpc.Context) { // FIXME: // var extraTransitions []core.Transition switch incomingTransition.Type { + case core.TransitionTypeMutualLock, core.TransitionTypeEscrowLock: + // Reject before node signs. Home channel must be materialized onchain + // before co-signing escrow transitions — onchain _isChannelHomeChain() + // returns false while status == VOID, so initiateEscrowDeposit() on the + // home chain would not take the home-chain path until createChannel() runs. + if *channelStatus != core.ChannelStatusOpen { + return rpc.Errorf("home channel is not materialized onchain") + } + return rpc.Errorf("transition is not supported yet") case core.TransitionTypeEscrowDeposit, core.TransitionTypeEscrowWithdraw, core.TransitionTypeMigrate: return rpc.Errorf("transition is not supported yet") // latestStateVersion := currentState.Version @@ -132,6 +150,12 @@ func (h *Handler) SubmitState(c *rpc.Context) { nodeSig = _nodeSig.String() incomingState.NodeSig = &nodeSig + // Store user state early — it's fully validated and signed at this point. + // The wrapping DB transaction ensures rollback if any subsequent step fails. + if err := tx.StoreUserState(incomingState, applicationID); err != nil { + return rpc.Errorf("failed to store user state: %v", err) + } + if incomingTransition.Type != core.TransitionTypeAcknowledgement { var transaction *core.Transaction switch incomingTransition.Type { @@ -143,7 +167,7 @@ func (h *Handler) SubmitState(c *rpc.Context) { } case core.TransitionTypeTransferSend: - newReceiverState, err := h.issueTransferReceiverState(ctx, tx, incomingState) + newReceiverState, err := h.issueTransferReceiverState(ctx, tx, incomingState, applicationID) if err != nil { return rpc.Errorf("failed to issue receiver state: %v", err) } @@ -152,6 +176,13 @@ func (h *Handler) SubmitState(c *rpc.Context) { return rpc.Errorf("failed to create transaction: %v", err) } case core.TransitionTypeMutualLock: + // Require home channel materialized onchain before co-signing a MutualLock. + // Onchain _isChannelHomeChain() returns false while status == VOID, so + // initiateEscrowDeposit() on the home chain would not take the home-chain + // path until the prior creation/checkpoint state is submitted via createChannel(). + if *channelStatus != core.ChannelStatusOpen { + return rpc.Errorf("home channel is not materialized onchain") + } return rpc.Errorf("transition is not supported yet") // if err := h.createEscrowChannel(tx, incomingState); err != nil { // return err @@ -162,6 +193,12 @@ func (h *Handler) SubmitState(c *rpc.Context) { // return rpc.Errorf("failed to create transaction: %v", err) // } case core.TransitionTypeEscrowLock: + // Require home channel materialized onchain before co-signing an EscrowLock. + // Same reason as MutualLock — the onchain home-chain path depends on the home + // channel having been created via createChannel() first. + if *channelStatus != core.ChannelStatusOpen { + return rpc.Errorf("home channel is not materialized onchain") + } return rpc.Errorf("transition is not supported yet") // if err := h.createEscrowChannel(tx, incomingState); err != nil { // return err @@ -198,10 +235,26 @@ func (h *Handler) SubmitState(c *rpc.Context) { // } // logger.Info("extra state issued", "userID", extraState.UserWallet, "asset", extraState.Asset, "version", extraState.Version) case core.TransitionTypeFinalize: + if *channelStatus != core.ChannelStatusOpen { + return rpc.Errorf("home channel is not materialized onchain") + } transaction, err = core.NewTransactionFromTransition(&incomingState, nil, incomingTransition) if err != nil { return rpc.Errorf("failed to create transaction: %v", err) } + // Atomically mark the channel as Closing so no further user-initiated state + // transitions are accepted until the on-chain close event is confirmed. + channel, err := tx.GetChannelByID(*incomingState.HomeChannelID) + if err != nil { + return rpc.Errorf("failed to get channel for finalize: %v", err) + } + if channel == nil { + return rpc.Errorf("channel not found for finalize: %s", *incomingState.HomeChannelID) + } + channel.Status = core.ChannelStatusClosing + if err := tx.UpdateChannel(*channel); err != nil { + return rpc.Errorf("failed to update channel status to closing: %v", err) + } case core.TransitionTypeMigrate: return rpc.Errorf("transition is not supported yet") // extraState, err := h.issueExtraState(ctx, tx, incomingState) @@ -212,7 +265,7 @@ func (h *Handler) SubmitState(c *rpc.Context) { return rpc.Errorf("transition '%s' is not supported by this endpoint", incomingTransition.Type.String()) } - if err := tx.RecordTransaction(*transaction); err != nil { + if err := tx.RecordTransaction(*transaction, applicationID); err != nil { return rpc.Errorf("failed to record transaction") } @@ -225,10 +278,6 @@ func (h *Handler) SubmitState(c *rpc.Context) { "amount", transaction.Amount.String()) } - if err := tx.StoreUserState(incomingState); err != nil { - return rpc.Errorf("failed to store user state: %v", err) - } - // TODO: consider state checkpoint if channel is challenged return nil diff --git a/clearnode/api/channel_v1/submit_state_test.go b/nitronode/api/channel_v1/submit_state_test.go similarity index 61% rename from clearnode/api/channel_v1/submit_state_test.go rename to nitronode/api/channel_v1/submit_state_test.go index 75407f1d1..fb2ff4d38 100644 --- a/clearnode/api/channel_v1/submit_state_test.go +++ b/nitronode/api/channel_v1/submit_state_test.go @@ -2,7 +2,9 @@ package channel_v1 import ( "context" + "fmt" "strconv" + "strings" "testing" "github.com/shopspring/decimal" @@ -10,11 +12,45 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/nitronode/metrics" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" ) +func TestSubmitState_InvalidUserWallet_Rejected(t *testing.T) { + mockTxStore := new(MockStore) + + handler := &Handler{ + useStoreInTx: func(h StoreTxHandler) error { return h(mockTxStore) }, + metrics: metrics.NewNoopRuntimeMetricExporter(), + } + + reqPayload := rpc.ChannelsV1SubmitStateRequest{ + State: rpc.StateV1{ + UserWallet: "0xnot-a-valid-address", + Asset: "USDC", + Epoch: "1", + Version: "1", + Transition: rpc.TransitionV1{Amount: "0"}, + }, + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.submit_state", Payload: payload}, + } + + handler.SubmitState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr) + assert.Contains(t, respErr.Error(), "invalid user_wallet") + mockTxStore.AssertNotCalled(t, "LockUserState", mock.Anything, mock.Anything) +} + func TestSubmitState_TransferSend_Success(t *testing.T) { // Setup mockTxStore := new(MockStore) @@ -121,7 +157,7 @@ func TestSubmitState_TransferSend_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockTxStore.On("LockUserState", senderWallet, asset).Return(decimal.Zero, nil) - mockTxStore.On("CheckOpenChannel", senderWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", senderWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", senderWallet, asset, false).Return(currentSenderState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", senderWallet, asset).Return(nil) mockStatePacker.On("PackState", mock.Anything).Return(packedSenderState, nil).Maybe() @@ -129,14 +165,15 @@ func TestSubmitState_TransferSend_Success(t *testing.T) { // For issueTransferReceiverState mockTxStore.On("LockUserState", receiverWallet, asset).Return(decimal.Zero, nil) mockTxStore.On("GetLastUserState", receiverWallet, asset, false).Return(currentReceiverState, nil) - mockTxStore.On("GetLastUserState", receiverWallet, asset, true).Return(nil, nil) + mockTxStore.On("EnsureNoOngoingEscrowOperation", receiverWallet, asset).Return(nil) + mockTxStore.On("CheckActiveChannel", receiverWallet, asset).Return("0x03", core.ChannelStatusOpen, nil).Once() mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { // Verify receiver state return state.UserWallet == receiverWallet && state.Version == expectedReceiverState.Version && state.Transition.Type == core.TransitionTypeTransferReceive && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // For recordTransaction mockTxStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { @@ -144,7 +181,7 @@ func TestSubmitState_TransferSend_Success(t *testing.T) { tx.Amount.Equal(transferAmount) && tx.FromAccount == senderWallet && tx.ToAccount == receiverWallet - })).Return(nil) + }), mock.Anything).Return(nil) // For storing sender state mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { @@ -153,7 +190,7 @@ func TestSubmitState_TransferSend_Success(t *testing.T) { state.Version == incomingSenderState.Version && state.Transition.Type == core.TransitionTypeTransferSend && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingSenderState) @@ -179,13 +216,401 @@ func TestSubmitState_TransferSend_Success(t *testing.T) { // Assert assert.NotNil(t, ctx.Response.Payload) - var response rpc.ChannelsV1SubmitStateResponse - err = ctx.Response.Payload.Translate(&response) - require.NoError(t, err) - assert.Nil(t, ctx.Response.Error()) - assert.NotEmpty(t, response.Signature, "Node signature should be present") + var response rpc.ChannelsV1SubmitStateResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + assert.Nil(t, ctx.Response.Error()) + assert.NotEmpty(t, response.Signature, "Node signature should be present") + + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedSenderState, response.Signature) + + // Verify all mock expectations + mockTxStore.AssertExpectations(t) +} + +func TestSubmitState_TransferSend_ReceiverHomeChannelChallenged_NoNodeSig(t *testing.T) { + // When the receiver's home channel is in Challenged status the node must persist the + // receiver state row but must NOT node-sign it. Otherwise an attacker holding an + // expired or out-of-scope session key for the receiver could turn a dust transfer into + // a freshly node-signed state and use it to checkpoint the channel back to OPERATING, + // resetting the dispute timer indefinitely. + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, err := core.NewChannelDefaultSigner(mockSigner) + require.NoError(t, err) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWalletSigner, err := core.NewChannelDefaultSigner(userSigner) + require.NoError(t, err) + senderWallet := userSigner.PublicKey().Address().String() + receiverWallet := "0x0987654321098765432109876543210987654321" + asset := "USDC" + senderHomeChannel := "0xSenderHome" + receiverHomeChannel := "0xReceiverHome" + transferAmount := decimal.NewFromInt(100) + + currentSenderState := core.State{ + ID: core.GetStateID(senderWallet, asset, 1, 1), + Asset: asset, + UserWallet: senderWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &senderHomeChannel, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + }, + } + + incomingSenderState := currentSenderState.NextState() + transferSendTransition, err := incomingSenderState.ApplyTransferSendTransition(receiverWallet, transferAmount) + require.NoError(t, err) + + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + packedSenderState, _ := core.PackState(*incomingSenderState, mockAssetStore) + userSig, _ := userWalletSigner.Sign(packedSenderState) + userSigStr := userSig.String() + incomingSenderState.UserSig = &userSigStr + + currentReceiverState := core.State{ + ID: core.GetStateID(receiverWallet, asset, 1, 1), + Asset: asset, + UserWallet: receiverWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &receiverHomeChannel, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(200), + UserNetFlow: decimal.NewFromInt(200), + }, + } + + expectedReceiverState := currentReceiverState.NextState() + _, err = expectedReceiverState.ApplyTransferReceiveTransition(senderWallet, transferAmount, transferSendTransition.TxID) + require.NoError(t, err) + + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockTxStore.On("LockUserState", senderWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("CheckActiveChannel", senderWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) + mockTxStore.On("GetLastUserState", senderWallet, asset, false).Return(currentSenderState, nil) + mockTxStore.On("EnsureNoOngoingStateTransitions", senderWallet, asset).Return(nil) + mockStatePacker.On("PackState", mock.Anything).Return(packedSenderState, nil).Maybe() + + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == senderWallet && state.NodeSig != nil + }), mock.Anything).Return(nil) + + mockTxStore.On("LockUserState", receiverWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("GetLastUserState", receiverWallet, asset, false).Return(currentReceiverState, nil) + mockTxStore.On("EnsureNoOngoingEscrowOperation", receiverWallet, asset).Return(nil) + // Challenged status falls through CheckActiveChannel (status > Open) as a nil + // status pointer, so the issuance path stores the receiver row unsigned — the + // dust-credit checkpoint reset described in MF2-H01 stays blocked. + mockTxStore.On("CheckActiveChannel", receiverWallet, asset).Return("", nil, nil).Once() + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == receiverWallet && + state.Version == expectedReceiverState.Version && + state.Transition.Type == core.TransitionTypeTransferReceive && + state.NodeSig == nil + }), mock.Anything).Return(nil) + + mockTxStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + return tx.TxType == core.TransactionTypeTransfer && tx.Amount.Equal(transferAmount) + }), mock.Anything).Return(nil) + + rpcState := toRPCState(*incomingSenderState) + reqPayload := rpc.ChannelsV1SubmitStateRequest{State: rpcState} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.submit_state", Payload: payload}, + } + + handler.SubmitState(ctx) + + require.NotNil(t, ctx.Response) + require.Nil(t, ctx.Response.Error()) + mockTxStore.AssertExpectations(t) +} + +func TestSubmitState_TransferSend_ReceiverWithEscrowLock_Rejected(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + err := handler(mockTxStore) + if err != nil { + return err + } + return nil + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + // Test data - derive senderWallet from a user signer key + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + senderWallet := userSigner.PublicKey().Address().String() + receiverWallet := "0x0987654321098765432109876543210987654321" + asset := "USDC" + homeChannelID := "0xHomeChannel123" + transferAmount := decimal.NewFromInt(100) + + // Create sender's current state (before transfer) + currentSenderState := core.State{ + ID: core.GetStateID(senderWallet, asset, 1, 1), + Transition: core.Transition{}, + Asset: asset, + UserWallet: senderWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + EscrowLedger: nil, + UserSig: nil, + NodeSig: nil, + } + + // Create incoming sender state (with transfer send transition) + incomingSenderState := currentSenderState.NextState() + + // Apply the transfer send transition to update balances + _, err := incomingSenderState.ApplyTransferSendTransition(receiverWallet, transferAmount) + require.NoError(t, err) + + // Sign the incoming sender state with user's wallet signer (adds 0x01 prefix) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Once() + packedSenderState, _ := core.PackState(*incomingSenderState, mockAssetStore) + userSig, _ := userWalletSigner.Sign(packedSenderState) + userSigStr := userSig.String() + incomingSenderState.UserSig = &userSigStr + + // Create receiver's current state + currentReceiverState := core.State{ + ID: core.GetStateID(receiverWallet, asset, 1, 1), + Transition: core.Transition{}, + Asset: asset, + UserWallet: receiverWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(200), + UserNetFlow: decimal.NewFromInt(200), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + EscrowLedger: nil, + UserSig: nil, + NodeSig: nil, + } + + // Mock expectations + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) + mockTxStore.On("LockUserState", senderWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("CheckActiveChannel", senderWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) + mockTxStore.On("GetLastUserState", senderWallet, asset, false).Return(currentSenderState, nil) + mockTxStore.On("EnsureNoOngoingStateTransitions", senderWallet, asset).Return(nil) + mockStatePacker.On("PackState", mock.Anything).Return(packedSenderState, nil).Maybe() + + // Sender state is stored before the transition-specific logic + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == senderWallet && state.NodeSig != nil + }), mock.Anything).Return(nil) + + // For issueTransferReceiverState - receiver has an active escrow lock + mockTxStore.On("LockUserState", receiverWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("GetLastUserState", receiverWallet, asset, false).Return(currentReceiverState, nil) + mockTxStore.On("EnsureNoOngoingEscrowOperation", receiverWallet, asset).Return(fmt.Errorf("escrow lock is still ongoing")) + + // Create RPC request + rpcState := toRPCState(*incomingSenderState) + reqPayload := rpc.ChannelsV1SubmitStateRequest{ + State: rpcState, + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + rpcRequest := rpc.Message{ + Method: "channels.v1.submit_state", + Payload: payload, + } + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpcRequest, + } + + // Execute + handler.SubmitState(ctx) + + // Assert - should fail because receiver has an active escrow lock + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error when receiver has active escrow lock") + assert.Contains(t, respErr.Error(), "escrow lock is still ongoing") + + mockTxStore.AssertExpectations(t) +} + +func TestSubmitState_TransferSend_SameWalletCaseInsensitive_Rejected(t *testing.T) { + // Verify that the sender==receiver check is case-insensitive. + // Ethereum addresses are hex and may differ only in case (checksummed vs lowercased). + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + // Derive senderWallet from a real key — Address().String() returns checksummed (mixed case) + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + senderWallet := userSigner.PublicKey().Address().String() // checksummed, e.g. "0xAbCdEf..." + + // Use uppercased hex digits for the same address so the test guarantees a case mismatch. + // Without EqualFold, "0xAbCdEf..." != "0xABCDEF..." would bypass the self-transfer check. + receiverWallet := "0x" + strings.ToUpper(senderWallet[2:]) + + asset := "USDC" + homeChannelID := "0xHomeChannel123" + transferAmount := decimal.NewFromInt(100) + + currentSenderState := core.State{ + ID: core.GetStateID(senderWallet, asset, 1, 1), + Transition: core.Transition{}, + Asset: asset, + UserWallet: senderWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + } + + incomingSenderState := currentSenderState.NextState() + _, err := incomingSenderState.ApplyTransferSendTransition(receiverWallet, transferAmount) + require.NoError(t, err) + + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + packedSenderState, _ := core.PackState(*incomingSenderState, mockAssetStore) + userSig, _ := userWalletSigner.Sign(packedSenderState) + userSigStr := userSig.String() + incomingSenderState.UserSig = &userSigStr + + // Mock expectations — should reach the issueTransferReceiverState check + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockTxStore.On("LockUserState", senderWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("CheckActiveChannel", senderWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) + mockTxStore.On("GetLastUserState", senderWallet, asset, false).Return(currentSenderState, nil) + mockTxStore.On("EnsureNoOngoingStateTransitions", senderWallet, asset).Return(nil) + mockStatePacker.On("PackState", mock.Anything).Return(packedSenderState, nil).Maybe() + + // Sender state is stored before the transition-specific logic + mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + return state.UserWallet == senderWallet && state.NodeSig != nil + }), mock.Anything).Return(nil) + + rpcState := toRPCState(*incomingSenderState) + reqPayload := rpc.ChannelsV1SubmitStateRequest{ + State: rpcState, + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + rpcRequest := rpc.Message{ + Method: "channels.v1.submit_state", + Payload: payload, + } + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpcRequest, + } + + handler.SubmitState(ctx) + + // Should fail because sender and receiver are the same address (different case) + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error when sender and receiver are the same wallet") + assert.Contains(t, respErr.Error(), "sender and receiver wallets are the same") - // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -287,7 +712,7 @@ func TestSubmitState_EscrowLock_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(2), "0xTokenAddress").Return(uint8(6), nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) mockTxStore.On("GetChannelByID", homeChannelID).Return(&homeChannel, nil) @@ -303,13 +728,13 @@ func TestSubmitState_EscrowLock_Success(t *testing.T) { tx.FromAccount == homeChannelID && tx.ToAccount == escrowChannelID && tx.Amount.Equal(lockAmount) - })).Return(nil) + }), mock.Anything).Return(nil) mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { return state.UserWallet == userWallet && state.Version == incomingState.Version && state.Transition.Type == core.TransitionTypeEscrowLock && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -341,6 +766,9 @@ func TestSubmitState_EscrowLock_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -444,7 +872,7 @@ func TestSubmitState_EscrowWithdraw_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(2), "0xTokenAddress").Return(uint8(6), nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentUnsignedState, nil) mockTxStore.On("GetLastUserState", userWallet, asset, true).Return(currentSignedState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) @@ -456,7 +884,7 @@ func TestSubmitState_EscrowWithdraw_Success(t *testing.T) { tx.FromAccount == homeChannelID && tx.ToAccount == escrowChannelID && tx.Amount.Equal(withdrawAmount) - })).Return(nil) + }), mock.Anything).Return(nil) // Store incoming state with node signature mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { @@ -464,7 +892,7 @@ func TestSubmitState_EscrowWithdraw_Success(t *testing.T) { state.Version == incomingState.Version && state.Transition.Type == core.TransitionTypeEscrowWithdraw && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -496,6 +924,9 @@ func TestSubmitState_EscrowWithdraw_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -580,7 +1011,7 @@ func TestSubmitState_HomeDeposit_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) @@ -590,13 +1021,13 @@ func TestSubmitState_HomeDeposit_Success(t *testing.T) { tx.FromAccount == homeChannelID && tx.ToAccount == userWallet && tx.Amount.Equal(depositAmount) - })).Return(nil) + }), mock.Anything).Return(nil) mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { return state.UserWallet == userWallet && state.Version == incomingState.Version && state.Transition.Type == core.TransitionTypeHomeDeposit && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -628,6 +1059,9 @@ func TestSubmitState_HomeDeposit_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -712,7 +1146,7 @@ func TestSubmitState_HomeWithdrawal_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) @@ -722,14 +1156,14 @@ func TestSubmitState_HomeWithdrawal_Success(t *testing.T) { tx.FromAccount == userWallet && tx.ToAccount == homeChannelID && tx.Amount.Equal(withdrawalAmount) - })).Return(nil) + }), mock.Anything).Return(nil) mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { return state.UserWallet == userWallet && state.Version == incomingState.Version && state.Transition.Type == core.TransitionTypeHomeWithdrawal && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -761,6 +1195,9 @@ func TestSubmitState_HomeWithdrawal_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -863,7 +1300,7 @@ func TestSubmitState_MutualLock_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(2), "0xTokenAddress").Return(uint8(6), nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) mockTxStore.On("GetChannelByID", homeChannelID).Return(&homeChannel, nil) @@ -878,14 +1315,14 @@ func TestSubmitState_MutualLock_Success(t *testing.T) { tx.FromAccount == homeChannelID && tx.ToAccount == escrowChannelID && tx.Amount.Equal(lockAmount) - })).Return(nil) + }), mock.Anything).Return(nil) mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { return state.UserWallet == userWallet && state.Version == incomingState.Version && state.Transition.Type == core.TransitionTypeMutualLock && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -917,6 +1354,9 @@ func TestSubmitState_MutualLock_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -1021,7 +1461,7 @@ func TestSubmitState_EscrowDeposit_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(2), "0xTokenAddress").Return(uint8(6), nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentUnsignedState, nil) mockTxStore.On("GetLastUserState", userWallet, asset, true).Return(currentSignedState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) @@ -1033,7 +1473,7 @@ func TestSubmitState_EscrowDeposit_Success(t *testing.T) { tx.FromAccount == escrowChannelID && tx.ToAccount == userWallet && tx.Amount.Equal(depositAmount) - })).Return(nil) + }), mock.Anything).Return(nil) // Store incoming state with node signature mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { @@ -1042,7 +1482,7 @@ func TestSubmitState_EscrowDeposit_Success(t *testing.T) { state.Transition.Type == core.TransitionTypeEscrowDeposit && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -1074,6 +1514,9 @@ func TestSubmitState_EscrowDeposit_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) } @@ -1158,17 +1601,25 @@ func TestSubmitState_Finalize_Success(t *testing.T) { mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil) mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) + openChannel := &core.Channel{ + ChannelID: homeChannelID, + Status: core.ChannelStatusOpen, + } + mockTxStore.On("GetChannelByID", homeChannelID).Return(openChannel, nil) + mockTxStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == homeChannelID && ch.Status == core.ChannelStatusClosing + })).Return(nil) mockTxStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { return tx.TxType == core.TransactionTypeFinalize && tx.FromAccount == userWallet && tx.ToAccount == homeChannelID && tx.Amount.Equal(userBalance) - })).Return(nil) + }), mock.Anything).Return(nil) mockTxStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { return state.UserWallet == userWallet && state.Version == incomingState.Version && @@ -1177,7 +1628,7 @@ func TestSubmitState_Finalize_Success(t *testing.T) { state.Transition.Amount.Equal(userBalance) && state.HomeLedger.UserBalance.IsZero() && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -1209,6 +1660,9 @@ func TestSubmitState_Finalize_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations mockTxStore.AssertExpectations(t) @@ -1292,7 +1746,7 @@ func TestSubmitState_Acknowledgement_Success(t *testing.T) { // Mock expectations mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) - mockTxStore.On("CheckOpenChannel", userWallet, asset).Return("0x03", true, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusOpen, nil) mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil).Maybe() @@ -1303,7 +1757,7 @@ func TestSubmitState_Acknowledgement_Success(t *testing.T) { state.Version == incomingState.Version && state.Transition.Type == core.TransitionTypeAcknowledgement && state.NodeSig != nil - })).Return(nil) + }), mock.Anything).Return(nil) // Create RPC request rpcState := toRPCState(*incomingState) @@ -1335,9 +1789,280 @@ func TestSubmitState_Acknowledgement_Success(t *testing.T) { assert.Nil(t, ctx.Response.Error()) assert.NotEmpty(t, response.Signature, "Node signature should be present") + // Verify the node signature is valid and recoverable to the node address + VerifyNodeSignature(t, nodeAddress, packedState, response.Signature) + // Verify all mock expectations - notably RecordTransaction should NOT have been called mockTxStore.AssertExpectations(t) - mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything) + mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) +} + +func TestSubmitState_MutualLock_VoidHomeChannel_Rejected(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + homeChannelID := "0xHomeChannel123" + lockAmount := decimal.NewFromInt(100) + + currentState := core.State{ + ID: core.GetStateID(userWallet, asset, 1, 1), + Transition: core.Transition{}, + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + } + + incomingState := currentState.NextState() + _, err := incomingState.ApplyMutualLockTransition(2, "0xTokenAddress", lockAmount) + require.NoError(t, err) + + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + mockAssetStore.On("GetTokenDecimals", uint64(2), "0xTokenAddress").Return(uint8(6), nil).Maybe() + packedState, _ := core.PackState(*incomingState, mockAssetStore) + userSig, _ := userWalletSigner.Sign(packedState) + userSigStr := userSig.String() + incomingState.UserSig = &userSigStr + + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusVoid, nil) + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) + mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) + mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) + mockTxStore.On("StoreUserState", mock.Anything, mock.Anything).Return(nil).Maybe() + + rpcState := toRPCState(*incomingState) + reqPayload := rpc.ChannelsV1SubmitStateRequest{State: rpcState} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.submit_state", Payload: payload}, + } + + handler.SubmitState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error when home channel is Void") + assert.Contains(t, respErr.Error(), "home channel is not materialized onchain") + + mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "CreateChannel", mock.Anything) +} + +func TestSubmitState_EscrowLock_VoidHomeChannel_Rejected(t *testing.T) { + // Setup + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + minChallenge := uint32(3600) + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: minChallenge, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + homeChannelID := "0xHomeChannel123" + lockAmount := decimal.NewFromInt(100) + + currentState := core.State{ + ID: core.GetStateID(userWallet, asset, 1, 1), + Transition: core.Transition{}, + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.NewFromInt(0), + NodeNetFlow: decimal.NewFromInt(0), + }, + } + + incomingState := currentState.NextState() + _, err := incomingState.ApplyEscrowLockTransition(2, "0xTokenAddress", lockAmount) + require.NoError(t, err) + + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + mockAssetStore.On("GetTokenDecimals", uint64(2), "0xTokenAddress").Return(uint8(6), nil).Maybe() + packedState, _ := core.PackState(*incomingState, mockAssetStore) + userSig, _ := userWalletSigner.Sign(packedState) + userSigStr := userSig.String() + incomingState.UserSig = &userSigStr + + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("0x03", core.ChannelStatusVoid, nil) + mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(currentState, nil) + mockTxStore.On("EnsureNoOngoingStateTransitions", userWallet, asset).Return(nil) + mockStatePacker.On("PackState", mock.Anything).Return(packedState, nil) + mockTxStore.On("StoreUserState", mock.Anything, mock.Anything).Return(nil).Maybe() + + rpcState := toRPCState(*incomingState) + reqPayload := rpc.ChannelsV1SubmitStateRequest{State: rpcState} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.submit_state", Payload: payload}, + } + + handler.SubmitState(ctx) + + require.NotNil(t, ctx.Response) + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error when home channel is Void") + assert.Contains(t, respErr.Error(), "home channel is not materialized onchain") + + mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "CreateChannel", mock.Anything) + mockTxStore.AssertNotCalled(t, "ScheduleInitiateEscrowWithdrawal", mock.Anything, mock.Anything) +} + +// TestSubmitState_ClosingChannel_Rejected verifies that SubmitState rejects any transition +// when the channel is Closing or Challenged (status > ChannelStatusOpen), preventing new +// states from being accepted on a channel that has already been finalized off-chain. +func TestSubmitState_ClosingChannel_Rejected(t *testing.T) { + mockTxStore := new(MockStore) + mockMemoryStore := new(MockMemoryStore) + mockAssetStore := new(MockAssetStore) + mockSigner := NewMockSigner() + nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner) + nodeAddress := mockSigner.PublicKey().Address().String() + mockStatePacker := new(MockStatePacker) + + handler := &Handler{ + stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), + statePacker: mockStatePacker, + useStoreInTx: func(h StoreTxHandler) error { + return h(mockTxStore) + }, + memoryStore: mockMemoryStore, + nodeSigner: nodeSigner, + nodeAddress: nodeAddress, + minChallenge: 3600, + metrics: metrics.NewNoopRuntimeMetricExporter(), + maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, + } + + userSigner := NewMockSigner() + userWalletSigner, _ := core.NewChannelDefaultSigner(userSigner) + userWallet := userSigner.PublicKey().Address().String() + asset := "USDC" + homeChannelID := "0xHomeChannel123" + + currentState := core.State{ + ID: core.GetStateID(userWallet, asset, 1, 1), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + TokenAddress: "0xTokenAddress", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.NewFromInt(500), + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + incomingState := currentState.NextState() + _, err := incomingState.ApplyHomeDepositTransition(decimal.NewFromInt(100)) + require.NoError(t, err) + + mockAssetStore.On("GetTokenDecimals", uint64(1), "0xTokenAddress").Return(uint8(6), nil).Maybe() + packedState, _ := core.PackState(*incomingState, mockAssetStore) + userSig, _ := userWalletSigner.Sign(packedState) + userSigStr := userSig.String() + incomingState.UserSig = &userSigStr + + mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + // CheckActiveChannel returns nil status for Closing/Challenged channels (status > ChannelStatusOpen). + mockTxStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockTxStore.On("CheckActiveChannel", userWallet, asset).Return("", nil, nil) + + rpcState := toRPCState(*incomingState) + payload, err := rpc.NewPayload(rpc.ChannelsV1SubmitStateRequest{State: rpcState}) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "channels.v1.submit_state", Payload: payload}, + } + + handler.SubmitState(ctx) + + respErr := ctx.Response.Error() + require.NotNil(t, respErr, "Expected error when channel is Closing") + assert.Contains(t, respErr.Error(), "user has no active channel") + + mockTxStore.AssertNotCalled(t, "GetLastUserState", mock.Anything, mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "StoreUserState", mock.Anything, mock.Anything) + mockTxStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) } // Helper function to create a string pointer diff --git a/clearnode/api/channel_v1/testing.go b/nitronode/api/channel_v1/testing.go similarity index 68% rename from clearnode/api/channel_v1/testing.go rename to nitronode/api/channel_v1/testing.go index 78c3ab245..fed42034c 100644 --- a/clearnode/api/channel_v1/testing.go +++ b/nitronode/api/channel_v1/testing.go @@ -1,14 +1,18 @@ package channel_v1 import ( + "strings" + "testing" "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/stretchr/testify/require" - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/sign" ) @@ -37,13 +41,18 @@ func (m *MockStore) GetLastUserState(wallet, asset string, signed bool) (*core.S return &state, args.Error(1) } -func (m *MockStore) CheckOpenChannel(wallet, asset string) (string, bool, error) { +func (m *MockStore) CheckActiveChannel(wallet, asset string) (string, *core.ChannelStatus, error) { args := m.Called(wallet, asset) - return args.String(0), args.Bool(1), args.Error(2) + var status *core.ChannelStatus + if v := args.Get(1); v != nil { + s := v.(core.ChannelStatus) + status = &s + } + return args.String(0), status, args.Error(2) } -func (m *MockStore) StoreUserState(state core.State) error { - args := m.Called(state) +func (m *MockStore) StoreUserState(state core.State, applicationID string) error { + args := m.Called(state, applicationID) return args.Error(0) } @@ -52,13 +61,18 @@ func (m *MockStore) EnsureNoOngoingStateTransitions(wallet, asset string) error return args.Error(0) } +func (m *MockStore) EnsureNoOngoingEscrowOperation(wallet, asset string) error { + args := m.Called(wallet, asset) + return args.Error(0) +} + func (m *MockStore) ScheduleInitiateEscrowWithdrawal(stateID string, chainID uint64) error { args := m.Called(stateID, chainID) return args.Error(0) } -func (m *MockStore) RecordTransaction(tx core.Transaction) error { - args := m.Called(tx) +func (m *MockStore) RecordTransaction(tx core.Transaction, applicationID string) error { + args := m.Called(tx, applicationID) return args.Error(0) } @@ -83,6 +97,24 @@ func (m *MockStore) GetActiveHomeChannel(wallet, asset string) (*core.Channel, e return args.Get(0).(*core.Channel), args.Error(1) } +func (m *MockStore) GetNotClosedHomeChannel(wallet, asset string) (*core.Channel, error) { + args := m.Called(wallet, asset) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.Channel), args.Error(1) +} + +func (m *MockStore) UpdateChannel(channel core.Channel) error { + args := m.Called(channel) + return args.Error(0) +} + +func (m *MockStore) HasNonClosedHomeChannel(wallet, asset string) (bool, error) { + args := m.Called(wallet, asset) + return args.Bool(0), args.Error(1) +} + func (m *MockStore) GetUserChannels(wallet string, status *core.ChannelStatus, asset *string, channelType *core.ChannelType, limit, offset uint32) ([]core.Channel, uint32, error) { args := m.Called(wallet, status, asset, channelType, limit, offset) if args.Get(0) == nil { @@ -91,6 +123,20 @@ func (m *MockStore) GetUserChannels(wallet string, status *core.ChannelStatus, a return args.Get(0).([]core.Channel), args.Get(1).(uint32), args.Error(2) } +func (m *MockStore) LockSessionKeyState(userAddress, sessionKey string, kind database.SessionKeyKind) (uint64, time.Time, error) { + args := m.Called(userAddress, sessionKey, kind) + var expiresAt time.Time + if v := args.Get(1); v != nil { + expiresAt = v.(time.Time) + } + return uint64(args.Int(0)), expiresAt, args.Error(2) +} + +func (m *MockStore) CountSessionKeysForUser(userAddress string) (uint32, error) { + args := m.Called(userAddress) + return uint32(args.Int(0)), args.Error(1) +} + func (m *MockStore) StoreChannelSessionKeyState(state core.ChannelSessionKeyStateV1) error { args := m.Called(state) return args.Error(0) @@ -101,12 +147,12 @@ func (m *MockStore) GetLastChannelSessionKeyVersion(wallet, sessionKey string) ( return args.Get(0).(uint64), args.Error(1) } -func (m *MockStore) GetLastChannelSessionKeyStates(wallet string, sessionKey *string) ([]core.ChannelSessionKeyStateV1, error) { - args := m.Called(wallet, sessionKey) +func (m *MockStore) GetLastChannelSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]core.ChannelSessionKeyStateV1, uint32, error) { + args := m.Called(wallet, sessionKey, includeInactive, limit, offset) if args.Get(0) == nil { - return nil, args.Error(1) + return nil, uint32(args.Int(1)), args.Error(2) } - return args.Get(0).([]core.ChannelSessionKeyStateV1), args.Error(1) + return args.Get(0).([]core.ChannelSessionKeyStateV1), uint32(args.Int(1)), args.Error(2) } func (m *MockStore) ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) { @@ -210,3 +256,14 @@ type MockActionGateway struct { func (m *MockActionGateway) AllowAction(_ action_gateway.Store, _ string, _ core.GatedAction) error { return m.Err } + +// VerifyNodeSignature verifies that a hex-encoded channel signature was produced by the expected node address. +func VerifyNodeSignature(t *testing.T, nodeAddr string, data []byte, sigHex string) { + t.Helper() + sigBytes, err := hexutil.Decode(sigHex) + require.NoError(t, err, "Failed to decode node signature") + + sigValidator := core.NewChannelSigValidator(nil) + err = sigValidator.Verify(strings.ToLower(nodeAddr), data, sigBytes) + require.NoError(t, err, "Node signature verification failed: expected signer %s", nodeAddr) +} diff --git a/clearnode/api/channel_v1/utils.go b/nitronode/api/channel_v1/utils.go similarity index 92% rename from clearnode/api/channel_v1/utils.go rename to nitronode/api/channel_v1/utils.go index d7e85bb72..364a6d775 100644 --- a/clearnode/api/channel_v1/utils.go +++ b/nitronode/api/channel_v1/utils.go @@ -135,6 +135,8 @@ func channelStatusToString(s core.ChannelStatus) string { return "open" case core.ChannelStatusChallenged: return "challenged" + case core.ChannelStatusClosing: + return "closing" case core.ChannelStatusClosed: return "closed" default: @@ -224,23 +226,25 @@ func unmapChannelSessionKeyStateV1(state *rpc.ChannelSessionKeyStateV1) (core.Ch } return core.ChannelSessionKeyStateV1{ - UserAddress: strings.ToLower(state.UserAddress), - SessionKey: strings.ToLower(state.SessionKey), - Version: version, - Assets: assets, - ExpiresAt: time.Unix(expiresAtUnix, 0), - UserSig: state.UserSig, + UserAddress: strings.ToLower(state.UserAddress), + SessionKey: strings.ToLower(state.SessionKey), + Version: version, + Assets: assets, + ExpiresAt: time.Unix(expiresAtUnix, 0), + UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, }, nil } // mapChannelSessionKeyStateV1 converts a core.ChannelSessionKeyStateV1 to an RPC ChannelSessionKeyStateV1. func mapChannelSessionKeyStateV1(state *core.ChannelSessionKeyStateV1) rpc.ChannelSessionKeyStateV1 { return rpc.ChannelSessionKeyStateV1{ - UserAddress: state.UserAddress, - SessionKey: state.SessionKey, - Version: strconv.FormatUint(state.Version, 10), - Assets: state.Assets, - ExpiresAt: strconv.FormatInt(state.ExpiresAt.Unix(), 10), - UserSig: state.UserSig, + UserAddress: state.UserAddress, + SessionKey: state.SessionKey, + Version: strconv.FormatUint(state.Version, 10), + Assets: state.Assets, + ExpiresAt: strconv.FormatInt(state.ExpiresAt.Unix(), 10), + UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, } } diff --git a/clearnode/api/metric_store.go b/nitronode/api/metric_store.go similarity index 72% rename from clearnode/api/metric_store.go rename to nitronode/api/metric_store.go index 2ea3e7601..42a475e85 100644 --- a/clearnode/api/metric_store.go +++ b/nitronode/api/metric_store.go @@ -1,8 +1,8 @@ package api import ( - "github.com/layer-3/nitrolite/clearnode/metrics" - "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" ) @@ -15,22 +15,22 @@ type metricStore struct { callbacks []func() } -func (s *metricStore) RecordTransaction(tx core.Transaction) error { - if err := s.DatabaseStore.RecordTransaction(tx); err != nil { +func (s *metricStore) RecordTransaction(tx core.Transaction, applicationID string) error { + if err := s.DatabaseStore.RecordTransaction(tx, applicationID); err != nil { return err } s.callbacks = append(s.callbacks, func() { - s.m.RecordTransaction(tx.Asset, tx.TxType, tx.Amount) + s.m.RecordTransaction(tx.Asset, tx.TxType, tx.Amount, applicationID) }) return nil } -func (s *metricStore) StoreUserState(state core.State) error { - if err := s.DatabaseStore.StoreUserState(state); err != nil { +func (s *metricStore) StoreUserState(state core.State, applicationID string) error { + if err := s.DatabaseStore.StoreUserState(state, applicationID); err != nil { return err } s.callbacks = append(s.callbacks, func() { - s.m.IncUserState(state.Asset, state.HomeLedger.BlockchainID, state.Transition.Type) + s.m.IncUserState(state.Asset, state.HomeLedger.BlockchainID, state.Transition.Type, applicationID) }) return nil } diff --git a/clearnode/api/node_v1/get_assets.go b/nitronode/api/node_v1/get_assets.go similarity index 100% rename from clearnode/api/node_v1/get_assets.go rename to nitronode/api/node_v1/get_assets.go diff --git a/clearnode/api/node_v1/get_assets_test.go b/nitronode/api/node_v1/get_assets_test.go similarity index 100% rename from clearnode/api/node_v1/get_assets_test.go rename to nitronode/api/node_v1/get_assets_test.go diff --git a/clearnode/api/node_v1/get_config.go b/nitronode/api/node_v1/get_config.go similarity index 100% rename from clearnode/api/node_v1/get_config.go rename to nitronode/api/node_v1/get_config.go diff --git a/clearnode/api/node_v1/get_config_test.go b/nitronode/api/node_v1/get_config_test.go similarity index 100% rename from clearnode/api/node_v1/get_config_test.go rename to nitronode/api/node_v1/get_config_test.go diff --git a/clearnode/api/node_v1/handler.go b/nitronode/api/node_v1/handler.go similarity index 100% rename from clearnode/api/node_v1/handler.go rename to nitronode/api/node_v1/handler.go diff --git a/clearnode/api/node_v1/interface.go b/nitronode/api/node_v1/interface.go similarity index 100% rename from clearnode/api/node_v1/interface.go rename to nitronode/api/node_v1/interface.go diff --git a/clearnode/api/node_v1/ping.go b/nitronode/api/node_v1/ping.go similarity index 100% rename from clearnode/api/node_v1/ping.go rename to nitronode/api/node_v1/ping.go diff --git a/clearnode/api/node_v1/testing.go b/nitronode/api/node_v1/testing.go similarity index 100% rename from clearnode/api/node_v1/testing.go rename to nitronode/api/node_v1/testing.go diff --git a/clearnode/api/node_v1/utils.go b/nitronode/api/node_v1/utils.go similarity index 100% rename from clearnode/api/node_v1/utils.go rename to nitronode/api/node_v1/utils.go diff --git a/nitronode/api/rate_limits.go b/nitronode/api/rate_limits.go new file mode 100644 index 000000000..33577b302 --- /dev/null +++ b/nitronode/api/rate_limits.go @@ -0,0 +1,59 @@ +package api + +import ( + "time" + + "github.com/layer-3/nitrolite/pkg/rpc" +) + +// rateLimitStorageKey is the per-connection SafeStorage key for the request-rate +// token bucket. The bucket is allocated on the first request and mutated in +// place on subsequent ones — processRequests dispatches the middleware chain +// serially per connection, so a single load is sufficient. +const rateLimitStorageKey = "rate_limiter" + +// tokenBucket holds the mutable state for per-connection rate limiting. +type tokenBucket struct { + tokens float64 + last time.Time +} + +// RateLimitMiddleware enforces a per-connection request-count token bucket. +// It complements the per-frame byte budget enforced by FrameRateLimiter at the +// connection layer: bytes guard bandwidth, this guards RPC throughput so a +// flood of small requests cannot bypass the byte cap. +// +// On overrun the request fails with an RPC error and the connection stays +// open; the byte limiter is the layer that closes connections. +func (r *RPCRouter) RateLimitMiddleware(c *rpc.Context) { + bucket := loadOrInitBucket(c, r.rateLimitBurst) + + now := time.Now() + bucket.tokens += now.Sub(bucket.last).Seconds() * r.rateLimitPerSec + if bucket.tokens > r.rateLimitBurst { + bucket.tokens = r.rateLimitBurst + } + bucket.last = now + + if bucket.tokens < 1 { + c.Fail(rpc.Errorf("rate limit exceeded"), "") + return + } + bucket.tokens-- + + c.Next() +} + +// loadOrInitBucket returns the bucket stored on the connection, allocating a +// fresh one pre-filled to burst on first use. The bucket is stored as a +// pointer; later mutations are visible without re-Set. +func loadOrInitBucket(c *rpc.Context, burst float64) *tokenBucket { + if v, ok := c.Storage.Get(rateLimitStorageKey); ok { + if b, ok := v.(*tokenBucket); ok { + return b + } + } + b := &tokenBucket{tokens: burst, last: time.Now()} + c.Storage.Set(rateLimitStorageKey, b) + return b +} diff --git a/clearnode/api/rate_limits_test.go b/nitronode/api/rate_limits_test.go similarity index 100% rename from clearnode/api/rate_limits_test.go rename to nitronode/api/rate_limits_test.go diff --git a/clearnode/api/rpc_router.go b/nitronode/api/rpc_router.go similarity index 82% rename from clearnode/api/rpc_router.go rename to nitronode/api/rpc_router.go index 73a4e2274..a4b160a9d 100644 --- a/clearnode/api/rpc_router.go +++ b/nitronode/api/rpc_router.go @@ -3,15 +3,15 @@ package api import ( "time" - "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/nitronode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/api/app_session_v1" + "github.com/layer-3/nitrolite/nitronode/api/apps_v1" + "github.com/layer-3/nitrolite/nitronode/api/channel_v1" + "github.com/layer-3/nitrolite/nitronode/api/node_v1" + "github.com/layer-3/nitrolite/nitronode/api/user_v1" + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/nitronode/store/database" + "github.com/layer-3/nitrolite/nitronode/store/memory" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/log" "github.com/layer-3/nitrolite/pkg/rpc" @@ -30,12 +30,15 @@ type RPCRouter struct { type RPCRouterConfig struct { NodeVersion string MinChallenge uint32 + MaxChallenge uint32 + AppRegistryEnabled bool MaxParticipants int MaxSessionDataLen int MaxAppMetadataLen int MaxRebalanceSignedUpdates int MaxSessionKeyIDs int + MaxSessionKeysPerUser int RateLimitPerSec float64 RateLimitBurst float64 @@ -47,7 +50,7 @@ func NewRPCRouter( signer sign.Signer, dbStore database.DatabaseStore, memoryStore memory.MemoryStore, - actionGateway *action_gateway.ActionGateway, + actionGateway action_gateway.ActionAllower, runtimeMetrics metrics.RuntimeMetricExporter, logger log.Logger, ) *RPCRouter { @@ -99,9 +102,9 @@ func NewRPCRouter( panic("failed to create channel wallet signer: " + err.Error()) } - 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) + channelV1Handler := channel_v1.NewHandler(useChannelV1StoreInTx, memoryStore, actionGateway, nodeChannelSigner, stateAdvancer, statePacker, nodeAddress, cfg.MinChallenge, cfg.MaxChallenge, runtimeMetrics, cfg.MaxSessionKeyIDs, cfg.MaxSessionKeysPerUser) + appSessionV1Handler := app_session_v1.NewHandler(useAppSessionV1StoreInTx, memoryStore, actionGateway, nodeChannelSigner, stateAdvancer, statePacker, nodeAddress, cfg.AppRegistryEnabled, runtimeMetrics, + cfg.MaxParticipants, cfg.MaxSessionDataLen, cfg.MaxSessionKeyIDs, cfg.MaxRebalanceSignedUpdates, cfg.MaxSessionKeysPerUser) appsV1Handler := apps_v1.NewHandler(dbStore, useAppV1StoreInTx, actionGateway, cfg.MaxAppMetadataLen) nodeV1Handler := node_v1.NewHandler(memoryStore, nodeAddress, cfg.NodeVersion) userV1Handler := user_v1.NewHandler(dbStore, useUserV1StoreInTx, actionGateway) @@ -134,6 +137,11 @@ func NewRPCRouter( nodeV1Group.Handle(rpc.NodeV1GetConfigMethod.String(), nodeV1Handler.GetConfig) appsV1Group := r.Node.NewGroup(rpc.AppsV1Group.String()) + if !cfg.AppRegistryEnabled { + appsV1Group.Use(func(c *rpc.Context) { + c.Fail(nil, "apps.v1 group is disabled") + }) + } appsV1Group.Handle(rpc.AppsV1GetAppsMethod.String(), appsV1Handler.GetApps) appsV1Group.Handle(rpc.AppsV1SubmitAppVersionMethod.String(), appsV1Handler.SubmitAppVersion) @@ -142,6 +150,11 @@ func NewRPCRouter( userV1Group.Handle(rpc.UserV1GetTransactionsMethod.String(), userV1Handler.GetTransactions) userV1Group.Handle(rpc.UserV1GetActionAllowancesMethod.String(), userV1Handler.GetActionAllowances) + // Pre-publish per-method metric series at 0 so dashboards and absent()-style + // alerts have defined values before any traffic arrives. Must run after all + // group.Handle calls so RegisteredMethods is complete. + runtimeMetrics.SeedRPCMethodMetrics(r.Node.RegisteredMethods(), MethodPathDomains()) + return r } @@ -153,6 +166,9 @@ func (r *RPCRouter) ObservabilityMiddleware(c *rpc.Context) { startTime := time.Now() methodPath := getMethodPath(c) + r.runtimeMetrics.IncRPCInflight(c.Request.Method) + defer r.runtimeMetrics.DecRPCInflight(c.Request.Method) + c.Next() reqDuration := time.Since(startTime) diff --git a/clearnode/api/user_v1/get_action_allowances.go b/nitronode/api/user_v1/get_action_allowances.go similarity index 100% rename from clearnode/api/user_v1/get_action_allowances.go rename to nitronode/api/user_v1/get_action_allowances.go diff --git a/clearnode/api/user_v1/get_action_allowances_test.go b/nitronode/api/user_v1/get_action_allowances_test.go similarity index 100% rename from clearnode/api/user_v1/get_action_allowances_test.go rename to nitronode/api/user_v1/get_action_allowances_test.go diff --git a/clearnode/api/user_v1/get_balances.go b/nitronode/api/user_v1/get_balances.go similarity index 78% rename from clearnode/api/user_v1/get_balances.go rename to nitronode/api/user_v1/get_balances.go index 44efc99f3..75a57a613 100644 --- a/clearnode/api/user_v1/get_balances.go +++ b/nitronode/api/user_v1/get_balances.go @@ -1,6 +1,7 @@ package user_v1 import ( + "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/rpc" ) @@ -12,6 +13,13 @@ func (h *Handler) GetBalances(c *rpc.Context) { return } + normalizedWallet, err := core.NormalizeHexAddress(req.Wallet) + if err != nil { + c.Fail(rpc.Errorf("invalid wallet: %v", err), "") + return + } + req.Wallet = normalizedWallet + balances, err := h.store.GetUserBalances(req.Wallet) if err != nil { c.Fail(err, "failed to retrieve balances") diff --git a/clearnode/api/user_v1/get_balances_test.go b/nitronode/api/user_v1/get_balances_test.go similarity index 68% rename from clearnode/api/user_v1/get_balances_test.go rename to nitronode/api/user_v1/get_balances_test.go index c07b08ccb..f0fbe3ae1 100644 --- a/clearnode/api/user_v1/get_balances_test.go +++ b/nitronode/api/user_v1/get_balances_test.go @@ -82,3 +82,31 @@ func TestGetBalances_Success(t *testing.T) { // Verify all mock expectations mockStore.AssertExpectations(t) } + +// TestGetBalances_NormalizesWallet verifies the wallet is normalized before the store call. +func TestGetBalances_NormalizesWallet(t *testing.T) { + mockStore := new(MockStore) + + handler := &Handler{ + store: mockStore, + } + + canonicalWallet := "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + mixedCaseWallet := "0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD" + + mockStore.On("GetUserBalances", canonicalWallet).Return([]core.BalanceEntry{}, nil) + + reqPayload := rpc.UserV1GetBalancesRequest{Wallet: mixedCaseWallet} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "user.v1.get_balances", Payload: payload}, + } + + handler.GetBalances(ctx) + + require.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} diff --git a/clearnode/api/user_v1/get_transactions.go b/nitronode/api/user_v1/get_transactions.go similarity index 79% rename from clearnode/api/user_v1/get_transactions.go rename to nitronode/api/user_v1/get_transactions.go index 9c7c1bd59..bdf6d54ba 100644 --- a/clearnode/api/user_v1/get_transactions.go +++ b/nitronode/api/user_v1/get_transactions.go @@ -13,6 +13,13 @@ func (h *Handler) GetTransactions(c *rpc.Context) { return } + normalizedWallet, err := core.NormalizeHexAddress(req.Wallet) + if err != nil { + c.Fail(rpc.Errorf("invalid wallet: %v", err), "") + return + } + req.Wallet = normalizedWallet + var paginationParams core.PaginationParams if req.Pagination != nil { paginationParams.Offset = req.Pagination.Offset @@ -23,7 +30,7 @@ func (h *Handler) GetTransactions(c *rpc.Context) { var transactions []core.Transaction var metadata core.PaginationMetadata - transactions, metadata, err := h.store.GetUserTransactions(req.Wallet, req.Asset, req.TxType, req.FromTime, req.ToTime, &paginationParams) + transactions, metadata, err = h.store.GetUserTransactions(req.Wallet, req.Asset, req.TxType, req.FromTime, req.ToTime, &paginationParams) if err != nil { c.Fail(err, "failed to retrieve transactions") return diff --git a/clearnode/api/user_v1/get_transactions_test.go b/nitronode/api/user_v1/get_transactions_test.go similarity index 77% rename from clearnode/api/user_v1/get_transactions_test.go rename to nitronode/api/user_v1/get_transactions_test.go index ed41441fa..0703dab1c 100644 --- a/clearnode/api/user_v1/get_transactions_test.go +++ b/nitronode/api/user_v1/get_transactions_test.go @@ -118,3 +118,39 @@ func TestGetTransactions_Success(t *testing.T) { // Verify all mock expectations mockStore.AssertExpectations(t) } + +// TestGetTransactions_NormalizesWallet verifies the wallet is normalized before the store call. +func TestGetTransactions_NormalizesWallet(t *testing.T) { + mockStore := new(MockStore) + + handler := &Handler{ + store: mockStore, + } + + canonicalWallet := "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + mixedCaseWallet := "0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD" + + mockStore.On( + "GetUserTransactions", + canonicalWallet, + (*string)(nil), + (*core.TransactionType)(nil), + (*uint64)(nil), + (*uint64)(nil), + &core.PaginationParams{}, + ).Return([]core.Transaction{}, core.PaginationMetadata{}, nil) + + reqPayload := rpc.UserV1GetTransactionsRequest{Wallet: mixedCaseWallet} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.Message{Method: "user.v1.get_transactions", Payload: payload}, + } + + handler.GetTransactions(ctx) + + require.Nil(t, ctx.Response.Error()) + mockStore.AssertExpectations(t) +} diff --git a/clearnode/api/user_v1/handler.go b/nitronode/api/user_v1/handler.go similarity index 100% rename from clearnode/api/user_v1/handler.go rename to nitronode/api/user_v1/handler.go diff --git a/clearnode/api/user_v1/interface.go b/nitronode/api/user_v1/interface.go similarity index 96% rename from clearnode/api/user_v1/interface.go rename to nitronode/api/user_v1/interface.go index 06f085a06..35044a811 100644 --- a/clearnode/api/user_v1/interface.go +++ b/nitronode/api/user_v1/interface.go @@ -1,7 +1,7 @@ package user_v1 import ( - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/action_gateway" "github.com/layer-3/nitrolite/pkg/core" ) diff --git a/clearnode/api/user_v1/testing.go b/nitronode/api/user_v1/testing.go similarity index 96% rename from clearnode/api/user_v1/testing.go rename to nitronode/api/user_v1/testing.go index be69f2c72..8bbd5d662 100644 --- a/clearnode/api/user_v1/testing.go +++ b/nitronode/api/user_v1/testing.go @@ -6,7 +6,7 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/mock" - "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/action_gateway" "github.com/layer-3/nitrolite/pkg/core" ) diff --git a/clearnode/api/user_v1/utils.go b/nitronode/api/user_v1/utils.go similarity index 96% rename from clearnode/api/user_v1/utils.go rename to nitronode/api/user_v1/utils.go index c8a5f0930..23b2c3831 100644 --- a/clearnode/api/user_v1/utils.go +++ b/nitronode/api/user_v1/utils.go @@ -23,6 +23,7 @@ func mapBalanceEntryV1(entry core.BalanceEntry) rpc.BalanceEntryV1 { return rpc.BalanceEntryV1{ Asset: entry.Asset, Amount: entry.Balance.String(), + Enforced: entry.Enforced.String(), } } diff --git a/nitronode/api/utils.go b/nitronode/api/utils.go new file mode 100644 index 000000000..55e53ad56 --- /dev/null +++ b/nitronode/api/utils.go @@ -0,0 +1,53 @@ +package api + +import ( + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func getMethodPath(c *rpc.Context) string { + switch c.Request.Method { + case rpc.AppSessionsV1SubmitAppStateMethod.String(): + var reqPayload rpc.AppSessionsV1SubmitAppStateRequest + if err := c.Request.Payload.Translate(&reqPayload); err != nil { + break + } + return reqPayload.AppStateUpdate.Intent.String() + case rpc.ChannelsV1RequestCreationMethod.String(): + var reqPayload rpc.ChannelsV1RequestCreationRequest + if err := c.Request.Payload.Translate(&reqPayload); err != nil { + break + } + return reqPayload.State.Transition.Type.String() + case rpc.ChannelsV1SubmitStateMethod.String(): + var reqPayload rpc.ChannelsV1SubmitStateRequest + if err := c.Request.Payload.Translate(&reqPayload); err != nil { + break + } + return reqPayload.State.Transition.Type.String() + } + + return "default" +} + +// MethodPathDomains returns the bounded `path` label domain per RPC method +// whose request payload determines path (matching getMethodPath's switch). +// Methods not in the map emit only path="default". Used by metrics seeding so +// absent()-style alerts have defined values for every (method, path, result) +// tuple at cold start, not just (method, "default", result). +func MethodPathDomains() map[string][]string { + intents := make([]string, 0, len(app.AllAppStateUpdateIntents)) + for _, i := range app.AllAppStateUpdateIntents { + intents = append(intents, i.String()) + } + transitions := make([]string, 0, len(core.AllTransitionTypes)) + for _, t := range core.AllTransitionTypes { + transitions = append(transitions, t.String()) + } + return map[string][]string{ + rpc.AppSessionsV1SubmitAppStateMethod.String(): intents, + rpc.ChannelsV1RequestCreationMethod.String(): transitions, + rpc.ChannelsV1SubmitStateMethod.String(): transitions, + } +} diff --git a/nitronode/api/utils_test.go b/nitronode/api/utils_test.go new file mode 100644 index 000000000..7be861c5d --- /dev/null +++ b/nitronode/api/utils_test.go @@ -0,0 +1,68 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +// TestMethodPathDomains_MatchesEnums fails if the bounded enums grow but +// MethodPathDomains is not updated, which would silently re-introduce the +// cold-start gap that the metric-seeding fix closes. +func TestMethodPathDomains_MatchesEnums(t *testing.T) { + domains := MethodPathDomains() + + intentMethod := rpc.AppSessionsV1SubmitAppStateMethod.String() + require.Contains(t, domains, intentMethod) + require.Len(t, domains[intentMethod], len(app.AllAppStateUpdateIntents)) + wantIntents := make([]string, 0, len(app.AllAppStateUpdateIntents)) + for _, i := range app.AllAppStateUpdateIntents { + wantIntents = append(wantIntents, i.String()) + } + assert.ElementsMatch(t, wantIntents, domains[intentMethod]) + + wantTransitions := make([]string, 0, len(core.AllTransitionTypes)) + for _, ttype := range core.AllTransitionTypes { + wantTransitions = append(wantTransitions, ttype.String()) + } + for _, m := range []string{ + rpc.ChannelsV1RequestCreationMethod.String(), + rpc.ChannelsV1SubmitStateMethod.String(), + } { + require.Contains(t, domains, m) + require.Len(t, domains[m], len(core.AllTransitionTypes)) + assert.ElementsMatch(t, wantTransitions, domains[m]) + } + + // No "unknown" sentinel ever leaks in — enum String() should cover every + // listed value, and any new enum value without a String() arm would land + // here as "unknown" and fail the test. + for _, paths := range domains { + for _, p := range paths { + assert.NotEqual(t, "unknown", p, "enum without String() arm in MethodPathDomains output") + } + } +} + +// TestMethodPathDomains_CoversGetMethodPathSwitch is a hand-maintained pairing +// between getMethodPath's switch arms and MethodPathDomains' map keys. If a +// new method gains payload-derived path logic in getMethodPath, this test +// fails until the same method is added to MethodPathDomains (and vice versa). +func TestMethodPathDomains_CoversGetMethodPathSwitch(t *testing.T) { + expected := []string{ + rpc.AppSessionsV1SubmitAppStateMethod.String(), + rpc.ChannelsV1RequestCreationMethod.String(), + rpc.ChannelsV1SubmitStateMethod.String(), + } + got := MethodPathDomains() + keys := make([]string, 0, len(got)) + for k := range got { + keys = append(keys, k) + } + assert.ElementsMatch(t, expected, keys) +} diff --git a/clearnode/blockchain_worker.go b/nitronode/blockchain_worker.go similarity index 81% rename from clearnode/blockchain_worker.go rename to nitronode/blockchain_worker.go index e63b4d9aa..a77923cc3 100644 --- a/clearnode/blockchain_worker.go +++ b/nitronode/blockchain_worker.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/log" ) @@ -37,20 +37,32 @@ const ( ) type BlockchainWorker struct { - blockchainID uint64 - client core.BlockchainClient - store BlockchainWorkerStore - logger log.Logger - metrics MetricsExporter + blockchainID uint64 + client core.BlockchainClient + store BlockchainWorkerStore + channelSigner core.ChannelSigner + assetStore core.AssetStore + logger log.Logger + metrics MetricsExporter } -func NewBlockchainWorker(blockchainID uint64, client core.BlockchainClient, store BlockchainWorkerStore, logger log.Logger, m MetricsExporter) *BlockchainWorker { +func NewBlockchainWorker( + blockchainID uint64, + client core.BlockchainClient, + store BlockchainWorkerStore, + channelSigner core.ChannelSigner, + assetStore core.AssetStore, + logger log.Logger, + m MetricsExporter, +) *BlockchainWorker { return &BlockchainWorker{ - blockchainID: blockchainID, - client: client, - store: store, - logger: logger.WithName("bw").WithKV("blockchainID", blockchainID), - metrics: m, + blockchainID: blockchainID, + client: client, + store: store, + channelSigner: channelSigner, + assetStore: assetStore, + logger: logger.WithName("bw").WithKV("blockchainID", blockchainID), + metrics: m, } } @@ -166,6 +178,9 @@ func (w *BlockchainWorker) processAction(_ context.Context, action database.Bloc case database.ActionTypeCheckpoint: txHash, err = w.client.Checkpoint(*state) + case database.ActionTypeChallenge: + txHash, err = w.submitChallenge(*state) + // case database.ActionTypeInitiateEscrowDeposit: // txHash, err = w.processInitiateEscrow(state, w.client.InitiateEscrowDeposit) @@ -198,6 +213,20 @@ func (w *BlockchainWorker) processAction(_ context.Context, action database.Bloc return true } +// submitChallenge produces a node challenger signature for the given state and submits +// challengeChannel(...) on the channel's home blockchain. +func (w *BlockchainWorker) submitChallenge(state core.State) (string, error) { + packed, err := core.PackChallengeState(state, w.assetStore) + if err != nil { + return "", fmt.Errorf("failed to pack challenge state: %w", err) + } + challengerSig, err := w.channelSigner.Sign(packed) + if err != nil { + return "", fmt.Errorf("failed to sign challenge state: %w", err) + } + return w.client.Challenge(state, challengerSig, core.ChannelParticipantNode) +} + // func (w *BlockchainWorker) processInitiateEscrow(state *core.State, initiate func(core.ChannelDefinition, core.State) (string, error)) (string, error) { // if state.EscrowChannelID == nil { // return "", fmt.Errorf("state has no escrow channel ID") diff --git a/clearnode/blockchain_worker_test.go b/nitronode/blockchain_worker_test.go similarity index 100% rename from clearnode/blockchain_worker_test.go rename to nitronode/blockchain_worker_test.go diff --git a/nitronode/chart/.helmignore b/nitronode/chart/.helmignore new file mode 100644 index 000000000..063045509 --- /dev/null +++ b/nitronode/chart/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +config/ diff --git a/nitronode/chart/Chart.yaml b/nitronode/chart/Chart.yaml new file mode 100644 index 000000000..af080548a --- /dev/null +++ b/nitronode/chart/Chart.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v2 +description: Nitronode Helm chart +name: nitronode +version: 1.0.0 +appVersion: "1.3.0" diff --git a/nitronode/chart/README.md b/nitronode/chart/README.md new file mode 100644 index 000000000..8bd9587c9 --- /dev/null +++ b/nitronode/chart/README.md @@ -0,0 +1,190 @@ +# nitronode + +![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) + +Nitronode Helm chart + +## Prerequisites + +- Kubernetes 1.24+ +- Helm 3.0+ +- For TLS: cert-manager installed in the cluster + +## Installing the Chart + +To install the chart with the release name `my-release`: +```bash +helm install my-release git+https://github.com/layer-3/nitrolite@nitronode/chart?ref=main +``` + +The command deploys Nitronode on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: +```bash +helm delete my-release +``` + +## Parameters + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| fullnameOverride | string | `""` | Override the full name | +| config.args | list | `["nitronode"]` | List of arguments to pass to the container | +| config.logLevel | string | `"info"` | Log level (info, debug, warn, error) | +| config.database.driver | string | `"sqlite"` | Database driver (sqlite, postgres) | +| config.database.path | string | `"clearnet.db?cache=shared"` | Database path (for sqlite) | +| config.database.host | string | `""` | Database host | +| config.database.port | int | `5432` | Database port | +| config.database.name | string | `"nitronode"` | Database name | +| config.database.user | string | `"changeme"` | Database user | +| config.database.password | string | `"changeme"` | Database password | +| config.database.sslmode | string | `"disable"` | Database SSL mode (disable, require, verify-ca, verify-full) | +| config.gcpSaSecret | string | `""` | Name of the secret containing GCP SA Credentials (Optional) | +| config.extraEnvs | object | `{}` | Additional environment variables as key-value pairs | +| config.secretEnvs | object | `{}` | Additional environment variables to be stored in a secret | +| config.envSecret | string | `""` | Name of the secret containing environment variables | +| config.blockchains | string | `""` | Blockchains configuration | +| config.assets | string | `""` | Assets configuration | +| config.actionGateway | string | `""` | Action Gateway configuration | +| replicaCount | int | `1` | Number of replicas | +| image.repository | string | `"ghcr.io/layer-3/nitrolite/nitronode"` | Docker image repository | +| image.tag | string | `"v1.0.0-rc.0"` | Docker image tag | +| service.http.enabled | bool | `true` | Enable HTTP service | +| service.http.port | int | `7824` | HTTP service port | +| service.http.path | string | `"/ws"` | HTTP service path (used by ingress) | +| metrics.enabled | bool | `false` | Enable Prometheus metrics | +| metrics.podmonitoring.enabled | bool | `false` | Enable PodMonitoring for Managed Prometheus | +| metrics.port | int | `4242` | Metrics port | +| metrics.endpoint | string | `"/metrics"` | Metrics endpoint path | +| metrics.scrapeInterval | string | `"30s"` | Metrics scrape interval | +| probes.liveness.enabled | bool | `false` | Enable liveness probe | +| probes.liveness.type | string | `"tcp"` | Liveness probe type (http, tcp) | +| probes.readiness.enabled | bool | `false` | Enable readiness probe | +| probes.readiness.type | string | `"tcp"` | Readiness probe type (http, tcp) | +| resources.limits | object | `{}` | Resource limits | +| resources.requests | object | `{}` | Resource requests | +| serviceAccount.create | bool | `false` | Create a ServiceAccount resource | +| serviceAccount.annotations | object | `{}` | Annotations to add to the ServiceAccount (e.g. for GKE Workload Identity) | +| autoscaling.enabled | bool | `false` | Enable autoscaling | +| autoscaling.minReplicas | int | `2` | Minimum number of replicas | +| autoscaling.maxReplicas | int | `100` | Maximum number of replicas | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization | +| autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target memory utilization | +| networking.tlsClusterIssuer | string | `"zerossl-prod"` | TLS cluster issuer | +| networking.externalHostname | string | `"nitronode.example.com"` | External hostname for the gateway | +| networking.gateway.enabled | bool | `true` | Enable API gateway | +| networking.gateway.className | string | `"envoy-gateway"` | Gateway class name | +| networking.gateway.ipAddressName | string | `""` | GKE static IP address name (GKE only) | +| networking.ingress.enabled | bool | `false` | Enable ingress | +| networking.ingress.className | string | `"nginx"` | Ingress class name | +| networking.ingress.annotations | object | `{}` | Ingress annotations | +| networking.ingress.grpc | bool | `false` | Enable GRPC for ingress | +| networking.ingress.tls.enabled | bool | `false` | Enable TLS for ingress | +| imagePullSecret | string | `""` | Image pull secret name | +| ghcrPullDockerConfigJson | string | `""` | Base64-encoded .dockerconfigjson for GHCR pull secret (provided via SOPS-encrypted secrets) | +| nodeSelector | object | `{}` | Node selector | +| tolerations | list | `[]` | Tolerations | +| affinity | object | `{}` | Affinity settings | +| extraLabels | object | `{}` | Additional labels to add to all resources | +| debug.enabled | bool | `false` | Enable debug deployment (idle container for exec debugging) | +| debug.resources | object | see values.yaml | Resource requests/limits for debug container | +| stressTest.enabled | bool | `false` | Enable stress test pods (helm test) | +| stressTest.wsURL | string | `""` | Default WebSocket URL for all pods (defaults to in-cluster service) | +| stressTest.privateKey | string | `""` | Default private key for signing (optional, ephemeral key used if not set) | +| stressTest.connections | int | `10` | Default number of connections per test | +| stressTest.timeout | string | `"10m"` | Default per-test timeout | +| stressTest.maxErrorRate | string | `"0.01"` | Default max error rate threshold (0.01 = 1%) | +| stressTest.pods | list | see values.yaml | List of stress test pods to run | + +## WebSocket DoS hardening + +Defense layered top-down. Each layer sheds load before the next. + +### Cloudflare (recommended for public-facing envs) + +WAF Rate Limiting rules — production / sandbox only. Skip for `stress-v1` +(test traffic, no Cloudflare zone configured). + +Suggested rule: + +| Field | Value | +|------------|----------------------------------------------------------------------------------------| +| Match | `(http.host eq "" and http.request.uri.path eq "/v1/ws")` | +| Threshold | 60 requests per 1 minute per IP | +| Action | Block 10m | +| Counting | All HTTP statuses | + +Pair with Bot Fight Mode + Managed Challenge on the same hostname for low-rep +sources. + +### NGINX Ingress (per-IP, per-conn) + +The env templates already wire these annotations on the WebSocket Ingress: + +```yaml +nginx.ingress.kubernetes.io/limit-connections: "50" # concurrent / IP +nginx.ingress.kubernetes.io/limit-rps: "20" # new conns/s / IP +nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" +``` + +> Note: `proxy-body-size` (nginx `client_max_body_size`) intentionally not set. +> It applies to HTTP request bodies only; after the WebSocket upgrade the +> ingress proxies the TCP stream transparently and cannot enforce a per-frame +> size limit. Frame size is capped at the application layer +> (`NITRONODE_WS_MAX_MESSAGE_SIZE` → `SetReadLimit`). + +**Real-IP requirement.** ingress-nginx must see the client IP, not the CF +edge IP or LB pod IP. Cluster-wide ConfigMap (one-time, ops-owned): + +```yaml +use-forwarded-headers: "true" +compute-full-forwarded-for: "true" +forwarded-for-header: "CF-Connecting-IP" # if behind Cloudflare +proxy-real-ip-cidr: "" +``` + +Without this, all traffic appears to come from a handful of LB IPs and the +per-IP limiters are useless. + +For envs without Cloudflare in front (e.g. `stress-v1`), ensure the +ingress-nginx Service has `externalTrafficPolicy: Local` so the GCP LB +preserves source IPs to the pods. + +### Application (`pkg/rpc`) + +Per-connection caps configured via env on the nitronode pod: + +| Env | Default | Purpose | +|----------------------------------|-------------|--------------------------------------------------------------------------| +| `NITRONODE_WS_MAX_MESSAGE_SIZE` | `131072` | Hard cap on inbound frame (bytes). Exceeded → close 1009 before alloc. | +| `NITRONODE_WS_BYTES_PER_SEC` | `262144` | Steady-state byte budget per connection. Set ≤ 0 to disable. | +| `NITRONODE_WS_BYTES_BURST` | `1048576` | Burst capacity for the per-connection byte bucket. | + +Disable the byte-rate cap (`WS_BYTES_PER_SEC=-1`) for canary rollout if +false positives are suspected. The frame size cap stays on regardless. + +## Gateway Configuration + +By default, the chart creates an API Gateway and configures it to use TLS via cert-manager. To use this feature: + +1. Create a cert-manager ClusterIssuer +2. Configure `networking.tlsClusterIssuer` with the issuer name +3. Set `networking.externalHostname` to your domain name + +> **Warning**: The Gateway currently does not support configurations with a static IP address. Ensure that your setup uses a dynamic DNS or hostname for proper functionality. Alternatively, you can configure an ingress resource to use a static IP address if required. + +## Troubleshooting + +### Common Issues + +- **Database Connection Issues**: Ensure the database connection URL is correct and the database is accessible from the cluster +- **TLS Certificate Issues**: Check cert-manager logs for problems with certificate issuance +- **Blockchain Connection Issues**: Verify RPC endpoint URLs are correct and accessible + +For more detailed debugging, check the application logs: + +```bash +kubectl logs -l app=nitronode +``` diff --git a/nitronode/chart/config/prod-v1/assets.yaml b/nitronode/chart/config/prod-v1/assets.yaml new file mode 100644 index 000000000..c2984cefe --- /dev/null +++ b/nitronode/chart/config/prod-v1/assets.yaml @@ -0,0 +1,58 @@ +assets: +# Alphabetically sorted by symbol (case insensitive) + - name: "Ether" + symbol: "eth" + decimals: 18 + suggested_blockchain_id: 59144 + tokens: + - blockchain_id: 59144 + address: "0x0000000000000000000000000000000000000000" + decimals: 18 + - name: "Tether USD" + symbol: "usdt" + decimals: 6 + suggested_blockchain_id: 1 + tokens: + - blockchain_id: 1 + address: "0xdac17f958d2ee523a2206206994597c13d831ec7" + decimals: 6 + - blockchain_id: 14 + address: "0xe7cd86e13AC4309349F30B3435a9d337750fC82D" + decimals: 6 + - blockchain_id: 56 + address: "0x55d398326f99059fF775485246999027B3197955" + decimals: 18 + - blockchain_id: 137 + address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" + decimals: 6 + # - blockchain_id: 480 + # address: "" + # decimals: 6 + - blockchain_id: 8453 + address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2" + decimals: 6 + - blockchain_id: 59144 + address: "0xA219439258ca9da29E9Cc4cE5596924745e12B93" + decimals: 6 + - blockchain_id: 1440000 + address: "0x9F8CF9c00fac501b3965872f4ed3271f6f4d06fF" + decimals: 6 + - name: "XRP" + symbol: "xrp" + decimals: 6 + suggested_blockchain_id: 1440000 + tokens: + - blockchain_id: 14 + address: "0xAd552A648C74D49E10027AB8a618A3ad4901c5bE" # fXRP + decimals: 6 + - blockchain_id: 1440000 + address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + decimals: 18 + - name: "Yellow" + symbol: "yellow" + decimals: 18 + suggested_blockchain_id: 1 + tokens: + - blockchain_id: 1 + address: "0x236eb848c95b231299b4aa9f56c73d6893462720" + decimals: 18 diff --git a/nitronode/chart/config/prod-v1/blockchains.yaml b/nitronode/chart/config/prod-v1/blockchains.yaml new file mode 100644 index 000000000..0a78eeb14 --- /dev/null +++ b/nitronode/chart/config/prod-v1/blockchains.yaml @@ -0,0 +1,41 @@ +blockchains: +- name: "ethereum" + id: 1 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "flare" + id: 14 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "bsc" + id: 56 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "polygon" + id: 137 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "world_chain" + id: 480 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "base" + id: 8453 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "linea" + id: 59144 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" +- name: "xrpl_evm" + id: 1440000 + channel_hub_address: "0x1a2f750170474d4c54f8d318d9d4343588b4c4d1" + channel_hub_sig_validators: + 1: "0x8C00e739B9D22103774Cb32B21A203F6De31A2cC" diff --git a/nitronode/chart/config/prod-v1/nitronode.yaml.gotmpl b/nitronode/chart/config/prod-v1/nitronode.yaml.gotmpl new file mode 100644 index 000000000..dbfa0ba44 --- /dev/null +++ b/nitronode/chart/config/prod-v1/nitronode.yaml.gotmpl @@ -0,0 +1,95 @@ +{{- $profile := "medium" -}} +{{- $p := index .Values.profiles $profile -}} +config: + args: ["nitronode"] + logLevel: info + database: + driver: postgres + host: pgbouncer + port: 5432 + name: nitronode_prod_v1 + user: nitronode_prod_v1_admin + # pgbouncer runs in-cluster on a private network, so TLS is not required + # here. Override the chart's `require` default explicitly. + sslmode: disable + envSecret: nitronode-secret-env + extraEnvs: + NITRONODE_DATABASE_MAX_OPEN_CONNS: "{{ $p.database.maxOpenConns }}" + NITRONODE_DATABASE_MAX_IDLE_CONNS: "{{ $p.database.maxIdleConns }}" + NITRONODE_DATABASE_CONN_MAX_LIFETIME_SEC: "3600" + NITRONODE_DATABASE_CONN_MAX_IDLE_TIME_SEC: "600" + NITRONODE_SIGNER_TYPE: "gcp-kms" + NITRONODE_GCP_KMS_KEY_NAME: "projects/ynet-stage/locations/europe-central2/keyRings/clearnode-signers-eu/cryptoKeys/prod-v1-a/cryptoKeyVersions/1" + NITRONODE_MAX_PARTICIPANTS: "32" + NITRONODE_MAX_SESSION_DATA_LEN: "1024" + NITRONODE_MAX_SIGNED_UPDATES: "0" + NITRONODE_MAX_SESSION_KEY_IDS: "256" + # WebSocket DoS hardening. + # Frame cap: largest legit v1 RPC ≈ a few KB; 128 KiB leaves headroom. + # Byte budget: 256 KiB/s steady, 1 MiB burst. Comfortably absorbs reload + # reconnect storms (auth + subscribe ≈ 20 KB / tab) without throttling. + NITRONODE_WS_MAX_MESSAGE_SIZE: "131072" + NITRONODE_WS_BYTES_PER_SEC: "262144" + NITRONODE_WS_BYTES_BURST: "1048576" + +image: + repository: ghcr.io/layer-3/nitrolite/nitronode + tag: v1.2.0 + +service: + http: + enabled: true + port: 7824 + path: /v1/ws + +serviceAccount: + create: true + annotations: + iam.gke.io/gcp-service-account: nitronode-prod-v1-agent@ynet-stage.iam.gserviceaccount.com + +metrics: + enabled: true + podmonitoring: + enabled: true + port: 4242 + endpoint: "/metrics" + +replicaCount: {{ $p.replicaCount }} +resources: + {{- toYaml $p.resources | nindent 2 }} + +autoscaling: + enabled: false + +networking: + externalHostname: nitronode.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" + # ── DoS hardening (per-IP, NGINX-level) ────────────────────────────── + # Caps abusive sources before WebSocket upgrade and JSON decode. + # Effective only when ingress-nginx sees real client IPs — see chart + # README "WebSocket DoS hardening" for required ConfigMap settings. + # prod-v1 has no Cloudflare in front; source IP comes from the GCP + # LB. Verify externalTrafficPolicy=Local on the ingress-nginx Service + # before relying on per-IP limits. + nginx.ingress.kubernetes.io/limit-connections: "50" # concurrent / IP + nginx.ingress.kubernetes.io/limit-rps: "20" # new conns/s / IP + nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" + # Note: nginx proxy-body-size applies to HTTP request bodies only. After + # the WebSocket upgrade, ingress-nginx proxies the TCP stream transparently + # and cannot enforce a per-frame size limit. The frame cap is enforced at + # the application layer via NITRONODE_WS_MAX_MESSAGE_SIZE / SetReadLimit. + +stressTest: + enabled: false diff --git a/nitronode/chart/config/prod-v1/secrets.yaml b/nitronode/chart/config/prod-v1/secrets.yaml new file mode 100644 index 000000000..b91c0bf3f --- /dev/null +++ b/nitronode/chart/config/prod-v1/secrets.yaml @@ -0,0 +1,21 @@ +config: + secretEnvs: + NITRONODE_BLOCKCHAIN_RPC_ETHEREUM: ENC[AES256_GCM,data:zhfrIdRR0RkV6Pp4jHPRVVuhY3bJuktqze8+m8bRDIuUO5j24SauEFl4Jywmlmj84HZlEtlgzolaIzkZhBNbaZVJDldN14s1EUg//7EcHk4YjKkSpxEetxbo1pu8uLIzlys9w6xbtFvRDpWtXUe8TnHuSawGvIII,iv:m86TzorjcWpL6ZKRl/8S0el026GI0aR9wZJqG9UD5tk=,tag:A+ibwZOp8z38HNWRUGumaA==,type:str] + NITRONODE_BLOCKCHAIN_RPC_BSC: ENC[AES256_GCM,data:l06oDM34tRjfFAnU5q+B58mxqOZ+cS98VjNFUQjpYtn2gEF4VNlnHCxvhF/dOYC0/3Gy4ehbNQDHxrBakNvDB1845iH+gGliW3qg3EY3K6rPaHPPO6daqY8eeMcJh0ZzzziKZvzIn+Gs3WlsrFnkWRaBCQ==,iv:AgpFQgcLjzaHgZM553/N//2b6L7DLWlMYD/zjxveZ7E=,tag:DGOWHEihP3+Iqf/e/gXn7w==,type:str] + NITRONODE_BLOCKCHAIN_RPC_FLARE: ENC[AES256_GCM,data:zco0BdiRLB/CqKVPGz+yqPUmBID2BSewOc33o7IRxoxiF6inKS3v4FnYy8fRnrT59yLP7YP1b3qeVW5q677ynF2smIZKGltVmtUSHBmGnA9Nq4zvL/l/INOLjQz9oYA19VDE0hCjry+LnrHIWOzukVT9jgOh9Bc50fVHKc3LOog=,iv:QwM7/JQ2rRXvFszIaGUKfbtp+TWu1YXaX5dHYSHemx8=,tag:ktGzeQE+r3DmrimVyFxCLw==,type:str] + NITRONODE_BLOCKCHAIN_RPC_POLYGON: ENC[AES256_GCM,data:VuMFBBuEyJp396ezU6S7BB7pa4kTjpYv1HrnoauMV4qGYku0+KFwSC33rhBem9ItrGymrc8CYHPvYT/p6S/F/TtnLFmR2No5p0PDXm5hG36y8K5CKieYogKe/LXIOxuW8PaS8EbWI+yJYrKzDDhroOc09h+lZck=,iv:aRlVhPDR/+s6wOWcC/q3Vphgu7M08/GTlceK6hILs+8=,tag:Q536y7/A8g+bjcLImWrNcA==,type:str] + NITRONODE_BLOCKCHAIN_RPC_WORLD_CHAIN: ENC[AES256_GCM,data:6VHtlxOhbf4R7r0ejaft0WB25Eb49YrjtkvXANz4HjFq+vApyKYanQlg/UJGAkJSGJlFY0Am43H8+2sB6/acYZvdaCgABmnLlD67NF1Kc6ghaKkCXxJRf5gvi8DsvFrvXUT09mT2SOqwy7VLCXiTRZ60seAgLagHbik=,iv:kJ8UAiOFOIp96TBtaeoVeqz+felGygOa+4tYfeFUofg=,tag:wT6xdPASDb5ANDysdHQTdQ==,type:str] + NITRONODE_BLOCKCHAIN_RPC_BASE: ENC[AES256_GCM,data:pRWvnuK7kPR+xQ/JXQxA1M01NTEo52El54sRMqv2tysXUgImtP33ecP9w7gTJWo8e/yVYGW7hu9ofodULNPr2m02eCh1FH4PUmfd9mq0JIe6WLZ7ChHkRwb0FyrjV2034kdH7yA0aXwbYPzLLt61HP21C28=,iv:9hXLGEZNjwJu7B/ucr3KtZBlQ8LUB8mc6HxMzGoB5Yg=,tag:sI/xPtAOdWMbiZCjbAY8Ug==,type:str] + NITRONODE_BLOCKCHAIN_RPC_XRPL_EVM: ENC[AES256_GCM,data:zpR4BwGzHBq/Z0+j9UKfoQ2xu+lo+pEQP0hlExFzyqgbl8doMA+lmSI3XsdfDYavuHgDyACuyR1MdoyzZp5zi0oGOYri/IaSsO1N2ICsEyaCciVVpyj1/UqT+oK85bEq9bS7rpINo9FQVXHzeH3j4pa4yn49wMA=,iv:2hmZ7BXbQ5Nyc0jnxSu/K6HobydAofVMXgXkZR4ixZg=,tag:j8sZetSzIWaWv94S5hTlFQ==,type:str] + NITRONODE_BLOCKCHAIN_RPC_LINEA: ENC[AES256_GCM,data:MldvSYHqHRj81dr9SoD70KxJRKYhQRRVWlzJyKLbqUKYOcmJlLIyjzUpWINtywmy1EpAbymxf3B4w034QwW3vAT/sh1WU1Hr0kj4sIx3RXSo2XZmHTnby9T5B7a1BS98g7OSzJawDXdwf/hu8lNF3myBHm/7,iv:2OmE0kwe06VhYzKwlbc0fRaxURoK7kBci9lwEgLwxXU=,tag:CZQl4rgx0OyawBCM4DbyDQ==,type:str] + database: + password: ENC[AES256_GCM,data:PzBnPyHb3CYiPA==,iv:1IEuAyzdb+ZMOdms6i6Pa2Emk6ifV0oK5faWnX7OZKY=,tag:Gl6/oVxjX1IEg70yjC+58Q==,type:str] +sops: + gcp_kms: + - resource_id: projects/ynet-stage/locations/global/keyRings/sops/cryptoKeys/sops-uat-key + created_at: "2026-04-02T12:14:10Z" + enc: CiQAMMK76XoOsaEq3elJXL/OGZ/OvdDWr3hiLdfb5oQzSqJDrqESSQC9etkIbM0LvrkQHLBIV6ENlIMxX5lz/YMxBp/J5pzkO6KG3TUQ20eQmBVovw0EI1lEox7J1GtLwENfBfcYFCy1us7df6/0JNI= + lastmodified: "2026-05-25T09:35:05Z" + mac: ENC[AES256_GCM,data:PnTq3BeaoNmI6nnIbi3wCqp7827mROtiynIR2//H/7VPrH74znAaueXMOQkiQNjh8VEnnpVwuVXiqc+PIlhAnZ2dE1LBaMYLifia+vDlQ6l4GHZzo5icaBJNyCDdfElfpDXrhI6vOMP8Ovy12SQCc9NmT+L34Y1eWz99NwRFwlM=,iv:Ihbpss6n00UKAGlkg7aFiLfZ0jErA7e5ghuQ+z6sd/I=,tag:Nkywu66+TGoHctm6ndH27w==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 diff --git a/nitronode/chart/config/profiles.yaml b/nitronode/chart/config/profiles.yaml new file mode 100644 index 000000000..a8c903065 --- /dev/null +++ b/nitronode/chart/config/profiles.yaml @@ -0,0 +1,78 @@ +# Resource sizing profiles for the nitronode release. +# +# Select one inline at the top of each env's nitronode.yaml.gotmpl: +# +# {{- $profile := "medium" -}} +# {{- $p := index .Values.profiles $profile -}} +# +# Then reference `$p.replicaCount`, `$p.resources`, `$p.database.*` where the +# chart previously had hardcoded values. +# +# ────────────────────────────────────────────────────────────────────────────── +# Tuning notes — use this when authoring a new profile +# ────────────────────────────────────────────────────────────────────────────── +# +# replicaCount Nitronode keeps signer state in-process. Running more +# than one replica today is a no-op at best and a +# correctness risk at worst — keep at 1 unless the +# runtime has been refactored for distribution. +# +# resources requests == limits (guaranteed QoS class in GKE, +# predictable scheduling, no throttling on burst). +# ephemeral-storage is the same across tiers — it covers +# logs + tmpfs, not workload data. +# +# database.maxOpenConns Upper bound on the Go DB pool's concurrent +# connections to pgbouncer. Grows with CPU: more +# CPU → more goroutines → more in-flight queries. +# Stays comfortably below pgbouncer's +# max_client_conn (2000 by default in our stack), +# so pgbouncer multiplexing absorbs the headroom. +# +# database.maxIdleConns Sized at ~20–25% of maxOpenConns so the pool +# reclaims fast on idle periods but doesn't thrash +# on bursty traffic. Must be <= maxOpenConns. +# +# Profiles intentionally exclude: +# - autoscaling: override per env when needed (requires nitronode to be +# stateless first; see replicaCount note). +# - podDisruptionBudget: with replicaCount=1 a PDB blocks voluntary drains. +# - DB conn lifetimes (CONN_MAX_LIFETIME_SEC, CONN_MAX_IDLE_TIME_SEC): +# policy, not resource-dependent. +# - any networking / ingress / service account bits: env identity, not sizing. +# +# ────────────────────────────────────────────────────────────────────────────── +# Profiles +# ────────────────────────────────────────────────────────────────────────────── + +profiles: + # Smoke/stress — minimal footprint, single replica, no HA. + tiny: + replicaCount: 1 + resources: + # CPU sized to 1.5× the derived bound (maxOpenConns × 0.01 CPU/conn). + requests: { cpu: 400m, memory: 200Mi, ephemeral-storage: 256Mi } + limits: { cpu: 400m, memory: 200Mi, ephemeral-storage: 256Mi } + database: + maxOpenConns: 25 + maxIdleConns: 6 + + # Baseline for active dev work — room to spare without over-provisioning. + light: + replicaCount: 1 + resources: + requests: { cpu: 750m, memory: 1Gi, ephemeral-storage: 256Mi } + limits: { cpu: 750m, memory: 1Gi, ephemeral-storage: 256Mi } + database: + maxOpenConns: 50 + maxIdleConns: 12 + + # Release-candidate / sandbox — sized for representative load. + medium: + replicaCount: 1 + resources: + requests: { cpu: 1500m, memory: 2Gi, ephemeral-storage: 256Mi } + limits: { cpu: 1500m, memory: 2Gi, ephemeral-storage: 256Mi } + database: + maxOpenConns: 100 + maxIdleConns: 25 diff --git a/nitronode/chart/config/sandbox-v1/assets.yaml b/nitronode/chart/config/sandbox-v1/assets.yaml new file mode 100644 index 000000000..0bac338e2 --- /dev/null +++ b/nitronode/chart/config/sandbox-v1/assets.yaml @@ -0,0 +1,59 @@ +assets: + - name: "Ether" + symbol: "eth" + decimals: 18 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 59141 + address: "0x0000000000000000000000000000000000000000" + decimals: 18 + - blockchain_id: 84532 + address: "0x0000000000000000000000000000000000000000" + decimals: 18 + - 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: "XRP" + symbol: "xrp" + decimals: 18 + suggested_blockchain_id: 1449000 + tokens: + - blockchain_id: 1449000 + address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + decimals: 18 + - name: "Yellow" + symbol: "yellow" + decimals: 18 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 11155111 + address: "0x236eB848C95b231299B4AA9f56c73D6893462720" + decimals: 18 + - name: "Yellow USD" + symbol: "yusd" + decimals: 6 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 59141 + address: "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde" + decimals: 18 + - blockchain_id: 80002 + address: "0x1b66e510B845a746334B866b62C64746bEbfF857" + decimals: 8 + - blockchain_id: 84532 + address: "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde" + decimals: 6 + - blockchain_id: 1449000 + address: "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde" + decimals: 18 + - blockchain_id: 11155111 + address: "0x67a5F0B0553E22689A539b80cBE4A1Cd126E2Bde" + decimals: 6 diff --git a/nitronode/chart/config/sandbox-v1/blockchains.yaml b/nitronode/chart/config/sandbox-v1/blockchains.yaml new file mode 100644 index 000000000..0df6b4f1f --- /dev/null +++ b/nitronode/chart/config/sandbox-v1/blockchains.yaml @@ -0,0 +1,26 @@ +blockchains: +- name: "linea_sepolia" + id: 59141 + channel_hub_address: "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + channel_hub_sig_validators: + 1: "0xB08901fb3c9952f9e33469169A1E3290FaD79e22" +- name: "polygon_amoy" + id: 80002 + channel_hub_address: "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + channel_hub_sig_validators: + 1: "0xB08901fb3c9952f9e33469169A1E3290FaD79e22" +- name: "base_sepolia" + id: 84532 + channel_hub_address: "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + channel_hub_sig_validators: + 1: "0xB08901fb3c9952f9e33469169A1E3290FaD79e22" +- name: "xrpl_evm_testnet" + id: 1449000 + channel_hub_address: "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + channel_hub_sig_validators: + 1: "0xB08901fb3c9952f9e33469169A1E3290FaD79e22" +- name: "ethereum_sepolia" + id: 11155111 + channel_hub_address: "0x5dba8515af063db0c243c15ece7b99f91459c7c3" + channel_hub_sig_validators: + 1: "0xB08901fb3c9952f9e33469169A1E3290FaD79e22" diff --git a/nitronode/chart/config/sandbox-v1/nitronode.yaml.gotmpl b/nitronode/chart/config/sandbox-v1/nitronode.yaml.gotmpl new file mode 100644 index 000000000..5caf6b4f9 --- /dev/null +++ b/nitronode/chart/config/sandbox-v1/nitronode.yaml.gotmpl @@ -0,0 +1,100 @@ +{{- $profile := "medium" -}} +{{- $p := index .Values.profiles $profile -}} +config: + args: ["nitronode"] + logLevel: info + database: + driver: postgres + host: pgbouncer + port: 5432 + name: nitronode_sandbox_v1 + user: nitronode_sandbox_v1_admin + # pgbouncer runs in-cluster on a private network, so TLS is not required + # here. Override the chart's `require` default explicitly. + sslmode: disable + envSecret: nitronode-secret-env + extraEnvs: + NITRONODE_DATABASE_MAX_OPEN_CONNS: "{{ $p.database.maxOpenConns }}" + NITRONODE_DATABASE_MAX_IDLE_CONNS: "{{ $p.database.maxIdleConns }}" + NITRONODE_DATABASE_CONN_MAX_LIFETIME_SEC: "3600" + NITRONODE_DATABASE_CONN_MAX_IDLE_TIME_SEC: "600" + NITRONODE_SIGNER_TYPE: "gcp-kms" + NITRONODE_GCP_KMS_KEY_NAME: "projects/ynet-stage/locations/europe-central2/keyRings/clearnode-signers-eu/cryptoKeys/sandbox-v1-b/cryptoKeyVersions/1" + NITRONODE_MAX_PARTICIPANTS: "32" + NITRONODE_MAX_SESSION_DATA_LEN: "1024" + NITRONODE_MAX_SIGNED_UPDATES: "0" + NITRONODE_MAX_SESSION_KEY_IDS: "256" + # Force fixed GasLimit on every blockchain tx (skip eth_estimateGas). + # Required for XRPL EVM testnet — its RPC rejects estimateGas with + # "gas cap cannot be lower than 21000". Worst-case observed on + # ChannelHub (initiateEscrowDeposit ≈ 660k) + ~50% headroom. + NITRONODE_BLOCKCHAIN_GAS_LIMIT: "1000000" + # WebSocket DoS hardening. + # Frame cap: largest legit v1 RPC ≈ a few KB; 128 KiB leaves headroom. + # Byte budget: 256 KiB/s steady, 1 MiB burst. Comfortably absorbs reload + # reconnect storms (auth + subscribe ≈ 20 KB / tab) without throttling. + NITRONODE_WS_MAX_MESSAGE_SIZE: "131072" + NITRONODE_WS_BYTES_PER_SEC: "262144" + NITRONODE_WS_BYTES_BURST: "1048576" + +image: + repository: ghcr.io/layer-3/nitrolite/nitronode + tag: v1.2.0 + +service: + http: + enabled: true + port: 7824 + path: /v1/ws + +serviceAccount: + create: true + annotations: + iam.gke.io/gcp-service-account: nitronode-sandbox-v1-agent@ynet-stage.iam.gserviceaccount.com + +metrics: + enabled: true + podmonitoring: + enabled: true + port: 4242 + endpoint: "/metrics" + +replicaCount: {{ $p.replicaCount }} +resources: + {{- toYaml $p.resources | nindent 2 }} + +autoscaling: + enabled: false + +networking: + externalHostname: nitronode-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" + # ── DoS hardening (per-IP, NGINX-level) ────────────────────────────── + # Caps abusive sources before WebSocket upgrade and JSON decode. + # Effective only when ingress-nginx sees real client IPs — see chart + # README "WebSocket DoS hardening" for required ConfigMap settings. + # sandbox-v1 has no Cloudflare in front; source IP comes from the GCP + # LB. Verify externalTrafficPolicy=Local on the ingress-nginx Service + # before relying on per-IP limits. + nginx.ingress.kubernetes.io/limit-connections: "50" # concurrent / IP + nginx.ingress.kubernetes.io/limit-rps: "20" # new conns/s / IP + nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" + # Note: nginx proxy-body-size applies to HTTP request bodies only. After + # the WebSocket upgrade, ingress-nginx proxies the TCP stream transparently + # and cannot enforce a per-frame size limit. The frame cap is enforced at + # the application layer via NITRONODE_WS_MAX_MESSAGE_SIZE / SetReadLimit. + +stressTest: + enabled: false diff --git a/nitronode/chart/config/sandbox-v1/secrets.yaml b/nitronode/chart/config/sandbox-v1/secrets.yaml new file mode 100644 index 000000000..7c11ad2c2 --- /dev/null +++ b/nitronode/chart/config/sandbox-v1/secrets.yaml @@ -0,0 +1,18 @@ +config: + secretEnvs: + NITRONODE_BLOCKCHAIN_RPC_ETHEREUM_SEPOLIA: ENC[AES256_GCM,data:C9eZOgyk62cjCaSGDNFEKU4G3Oei1yLV9ovrF7+2lMFwTz6CjQ5vmpAZtu7vSHJaR9xZZEmuvpymV4dmiOxHOdOq7FJM/tLGICcTrXV0GegS5GeCr7DrHlG6nAkgwwZDwRJqB1lqsB4MUIXxzaJptvPS27mzMEam,iv:JOV2bGKxSWgM0GiLEAbs0q9XZ9bx8lGo1anXcv4vN4w=,tag:M/l0RqzxfFkP4BJfuN83qA==,type:str] + NITRONODE_BLOCKCHAIN_RPC_BASE_SEPOLIA: ENC[AES256_GCM,data:eTP64hvtWJ9sDP77lNpPDn2boe67/YPJnFhYLKUbLU4wCqsAPkFpWNio9F9bexMXJEEnsMYA2hj7oMCMrIgy8SVnB/hky1sXZRvsz7vm0gFMW8fNM3QDHiTx9Jqi9LvrW/c=,iv:yYKcmxHcav53L+LJXO0gR6HF1hWvdOpwBOeQtR70yVM=,tag:502CTKA8k52mx3tVTCIF6A==,type:str] + NITRONODE_BLOCKCHAIN_RPC_XRPL_EVM_TESTNET: ENC[AES256_GCM,data:JQmX3pYtaGROWJGa+PRhYe6pPhUIOUY381oym2946BeTKpOtKTtyvZOtPoPp6PA6I0N9GiYSiqUpP0fMz/vyAhvdfXD7cRGIi8ZMCzP2u/m9hp7simQvilHe3TALoo7GBxXTWZGBxbxlvpj3Nwpdwbw92kRcdeI=,iv:88scr/lrB6O1KlraZ8K/NnppUgoLYlz657AZQ46y/sA=,tag:BC3cGCUDMfSHD0l+mNK+Fg==,type:str] + NITRONODE_BLOCKCHAIN_RPC_POLYGON_AMOY: ENC[AES256_GCM,data:DHgJGodvBtWjRtIgv+APVxGW526MeXyD0nXYLIkmuKsUXxitL7pcvibzHQZD6y0rblIYSe9XjNXjRcOOZsldhC2O71ql/PaYac+YYDxZ9hlqhRtLptXYuQRbI6Ip5YzIq4iAYm3nGH5QMrysKei0HcxPC7OcEYI=,iv:L5slsnjo5ML93xrUDaEoIgDw/iKyRxzk8ynrPel3D8s=,tag:S+cIE6dwZMHas7t74cd5Kw==,type:str] + NITRONODE_BLOCKCHAIN_RPC_LINEA_SEPOLIA: ENC[AES256_GCM,data:7g5tl+C75/Zm8BdOwhtQFh2j/nZ25/UV0g2MhXM6mtyMUBRzmqR5TxMfEOU9160Td+nI0N9kCDlY13askkZ/uwUmhLcvt6qnSxRCTWSsSLOWQwcqURaWmVArhqxJ1v0rTcaGjM54cnDCWQzh6dcUmm+5Mmkp,iv:zQ5TNP6MNm5dkB+W2jD9c5sG6CrTaLwGl0kFIu0rmnQ=,tag:sIF3hnyxic3CjSLY0eZTbw==,type:str] + database: + password: ENC[AES256_GCM,data:qxIsh1yaREqF39wKzR8y/+vqKwIySasu,iv:Cti2KIRswrfu52H6uEDFmUj1ft7jUK9si1yJBwvlbuY=,tag:Q8LAzXSqMnprD8t/IqnqXw==,type:str] +sops: + gcp_kms: + - resource_id: projects/ynet-stage/locations/global/keyRings/sops/cryptoKeys/sops-uat-key + created_at: "2026-04-02T12:14:10Z" + enc: CiQAMMK76XoOsaEq3elJXL/OGZ/OvdDWr3hiLdfb5oQzSqJDrqESSQC9etkIbM0LvrkQHLBIV6ENlIMxX5lz/YMxBp/J5pzkO6KG3TUQ20eQmBVovw0EI1lEox7J1GtLwENfBfcYFCy1us7df6/0JNI= + lastmodified: "2026-05-27T10:21:57Z" + mac: ENC[AES256_GCM,data:YqAe1j2457HUWO4dAGYUSCPathypZ9GdxnpXxnD5T0A8EuQCsS+1q7TNMnXsDb3SVrDHmQ1as7DModbnju/8Z0wMRkrHDFnXMFMFzjMsuqyEr+fcyWvwBMb0hndk808UhQ7MpACZS3oqQi3+9WAYSunMszzmDt3GWUUifJ21axY=,iv:hArF3HQfG+XVRg5HBnmbpwILLYXiXsQ9jntYLgFryiA=,tag:V6GLJLrEgP4EvzQS4ITgew==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 diff --git a/nitronode/chart/config/secrets.yaml b/nitronode/chart/config/secrets.yaml new file mode 100644 index 000000000..2a786665f --- /dev/null +++ b/nitronode/chart/config/secrets.yaml @@ -0,0 +1,10 @@ +ghcrPullDockerConfigJson: ENC[AES256_GCM,data:ymxEUjjpVkWv9wT6rX5HkYRLd9F+JQd9bzZCpnb/QUgDKL5sSAOYaPSJ5AhvEB2XiVgcz6L8Njs/w5s9OLIRI68LFu/Fp/gkzKVXRxDrrvr924UDCxoQh5Rd55kq2ClgxjaFk+YCq2YbsS2+6cQK3yBE0L1f8aOgU6H+pD0K1CHMd+x1bYv8ZnqeZOs52/OT3mFhblG6SLadWAVek7hA7CW5SlQ8XgIqpyb4Kavf5qCuNikvglzP/JFJEBGIvI+ha3V4eH4feyAf1DAjYD+NONpq9i6HerWf75BcXN/ljPnWtqV+Xjh3JVX2FFyn+5EVLDsyJkibkWuFHczVb9WaHRD1A8kQwzAiEf1jU9QUcII=,iv:95a54zK998kMBtDlKZrZf2acygFcnkQm/osBI3Urq8A=,tag:RRPNjFGTfHOcwtyDPBZGnA==,type:str] +sops: + gcp_kms: + - resource_id: projects/ynet-stage/locations/global/keyRings/sops/cryptoKeys/sops-uat-key + created_at: "2026-04-02T12:06:41Z" + enc: CiQAMMK76ezqx/QwM9G//M0kOwk3Fz777m7ay6W3kGDw/Vtb8qUSSQC9etkIkNWwThHRkMkNO2j6nxfI/TMWb476eAD15D449G7ZZQhp4JrQlX0PMQijyYU1s2LlfpwqntGV0Fg0vw871Z4J/g4rmZQ= + lastmodified: "2026-04-02T12:06:42Z" + mac: ENC[AES256_GCM,data:cMtClcwu85XndRef2a4PwqESMWKkQ0YNd0wPE722FqEGFgtzXnloHPftXNYjciDS+vqWubLj79gjcTlCSFurJcoAHiaLkJZ81TdPOxU7t10FlRjwPYSKKCXXJjg/rzJCqkRO0quRhOXznvyPKAuqrJBcTo0iPJtGjDjwZZOfKRI=,iv:CNkK4C/ATnhIGsL4YWnibaW0duls4BBGhYJiNTNBXKQ=,tag:24a7H3MVs4aw4uRmieygsw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 diff --git a/clearnode/chart/config/sandbox-v1/assets.yaml b/nitronode/chart/config/stress-v1/assets.yaml similarity index 73% rename from clearnode/chart/config/sandbox-v1/assets.yaml rename to nitronode/chart/config/stress-v1/assets.yaml index 20a78ce7c..b35a119ff 100644 --- a/clearnode/chart/config/sandbox-v1/assets.yaml +++ b/nitronode/chart/config/stress-v1/assets.yaml @@ -7,20 +7,20 @@ assets: - blockchain_id: 11155111 address: "0xD3E8Eb01Ae895262f187c4aAe936eC5c0665bbf8" decimals: 6 - - blockchain_id: 80002 - address: "0x0827b6aAA03475A8BF59Ee1A2bD76903DDFbaDB6" + - blockchain_id: 84532 + address: "0x541aa129B863bbd8A7176a1579b7558CACc06671" decimals: 8 - name: "BNB" symbol: "bnb" - decimals: 18 + decimals: 8 suggested_blockchain_id: 11155111 tokens: - blockchain_id: 11155111 address: "0x719a00F9e8b831335F156337cEF7dC48986b2e84" decimals: 18 - - blockchain_id: 80002 - address: "0x9d8193e5655a36FFB9CD7D88D31c91d2650896D0" - decimals: 18 + - blockchain_id: 84532 + address: "0x96dE620f629efd76935070f040A9B3AC855339EB" + decimals: 8 - name: "Ether" symbol: "eth" decimals: 18 @@ -29,12 +29,7 @@ assets: - blockchain_id: 11155111 address: "0x0000000000000000000000000000000000000000" decimals: 18 - - name: "POL" - symbol: "pol" - decimals: 18 - suggested_blockchain_id: 80002 - tokens: - - blockchain_id: 80002 + - blockchain_id: 84532 address: "0x0000000000000000000000000000000000000000" decimals: 18 - name: "Yellow" diff --git a/nitronode/chart/config/stress-v1/blockchains.yaml b/nitronode/chart/config/stress-v1/blockchains.yaml new file mode 100644 index 000000000..9ccb2de93 --- /dev/null +++ b/nitronode/chart/config/stress-v1/blockchains.yaml @@ -0,0 +1,11 @@ +blockchains: +- name: "ethereum_sepolia" + id: 11155111 + channel_hub_address: "0x7d61ec428cfae560f43647af567ea7c6e2cc5527" + channel_hub_sig_validators: + 1: "0xe2B33Aa3922d7ac386e6801Ae7D9498C4DF45F1f" +- name: "base_sepolia" + id: 84532 + channel_hub_address: "0x61b9e0767f2eca7e33802e82f9c64b1ebe72ba31" + channel_hub_sig_validators: + 1: "0x4085554a56F962b6c8eeeb017Bf2e9D2F3E31131" diff --git a/nitronode/chart/config/stress-v1/nitronode.yaml.gotmpl b/nitronode/chart/config/stress-v1/nitronode.yaml.gotmpl new file mode 100644 index 000000000..bf71eb7a5 --- /dev/null +++ b/nitronode/chart/config/stress-v1/nitronode.yaml.gotmpl @@ -0,0 +1,95 @@ +{{- $profile := "tiny" -}} +{{- $p := index .Values.profiles $profile -}} +config: + args: ["nitronode"] + logLevel: info + database: + driver: postgres + host: pgbouncer + port: 5432 + name: nitronode_stress_v1 + user: nitronode_stress_v1_admin + # pgbouncer runs in-cluster on a private network, so TLS is not required + # here. Override the chart's `require` default explicitly. + sslmode: disable + envSecret: nitronode-secret-env + extraEnvs: + NITRONODE_DATABASE_MAX_OPEN_CONNS: "{{ $p.database.maxOpenConns }}" + NITRONODE_DATABASE_MAX_IDLE_CONNS: "{{ $p.database.maxIdleConns }}" + NITRONODE_DATABASE_CONN_MAX_LIFETIME_SEC: "3600" + NITRONODE_DATABASE_CONN_MAX_IDLE_TIME_SEC: "600" + NITRONODE_SIGNER_TYPE: "gcp-kms" + NITRONODE_GCP_KMS_KEY_NAME: "projects/ynet-stage/locations/europe-central2/keyRings/clearnode-signers-eu/cryptoKeys/stress-v1-a/cryptoKeyVersions/1" + NITRONODE_MAX_PARTICIPANTS: "32" + NITRONODE_MAX_SESSION_DATA_LEN: "1024" + NITRONODE_MAX_SIGNED_UPDATES: "0" + NITRONODE_MAX_SESSION_KEY_IDS: "256" + # WebSocket DoS hardening. + # Frame cap: largest legit v1 RPC ≈ a few KB; 128 KiB leaves headroom. + # Byte budget: 256 KiB/s steady, 1 MiB burst. Comfortably absorbs reload + # reconnect storms (auth + subscribe ≈ 20 KB / tab) without throttling. + NITRONODE_WS_MAX_MESSAGE_SIZE: "131072" + NITRONODE_WS_BYTES_PER_SEC: "262144" + NITRONODE_WS_BYTES_BURST: "1048576" + +image: + repository: ghcr.io/layer-3/nitrolite/nitronode + tag: v1.2.0 + +service: + http: + enabled: true + port: 7824 + path: /v1/ws + +serviceAccount: + create: true + annotations: + iam.gke.io/gcp-service-account: nitronode-stress-v1-agent@ynet-stage.iam.gserviceaccount.com + +metrics: + enabled: true + podmonitoring: + enabled: true + port: 4242 + endpoint: "/metrics" + +replicaCount: {{ $p.replicaCount }} +resources: + {{- toYaml $p.resources | nindent 2 }} + +autoscaling: + enabled: false + +networking: + externalHostname: nitronode-stress.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" + # ── DoS hardening (per-IP, NGINX-level) ────────────────────────────── + # Caps abusive sources before WebSocket upgrade and JSON decode. + # Effective only when ingress-nginx sees real client IPs — see chart + # README "WebSocket DoS hardening" for required ConfigMap settings. + # stress-v1 has no Cloudflare in front; source IP comes from the GCP + # LB. Verify externalTrafficPolicy=Local on the ingress-nginx Service + # before relying on per-IP limits. + nginx.ingress.kubernetes.io/limit-connections: "50" # concurrent / IP + nginx.ingress.kubernetes.io/limit-rps: "20" # new conns/s / IP + nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" + # Note: nginx proxy-body-size applies to HTTP request bodies only. After + # the WebSocket upgrade, ingress-nginx proxies the TCP stream transparently + # and cannot enforce a per-frame size limit. The frame cap is enforced at + # the application layer via NITRONODE_WS_MAX_MESSAGE_SIZE / SetReadLimit. + +stressTest: + enabled: false diff --git a/nitronode/chart/config/stress-v1/secrets.yaml b/nitronode/chart/config/stress-v1/secrets.yaml new file mode 100644 index 000000000..a18171fda --- /dev/null +++ b/nitronode/chart/config/stress-v1/secrets.yaml @@ -0,0 +1,15 @@ +config: + secretEnvs: + NITRONODE_BLOCKCHAIN_RPC_ETHEREUM_SEPOLIA: ENC[AES256_GCM,data:C9eZOgyk62cjCaSGDNFEKU4G3Oei1yLV9ovrF7+2lMFwTz6CjQ5vmpAZtu7vSHJaR9xZZEmuvpymV4dmiOxHOdOq7FJM/tLGICcTrXV0GegS5GeCr7DrHlG6nAkgwwZDwRJqB1lqsB4MUIXxzaJptvPS27mzMEam,iv:JOV2bGKxSWgM0GiLEAbs0q9XZ9bx8lGo1anXcv4vN4w=,tag:M/l0RqzxfFkP4BJfuN83qA==,type:str] + NITRONODE_BLOCKCHAIN_RPC_BASE_SEPOLIA: ENC[AES256_GCM,data:sVLy5GPs3+hSr2adf2EZksHw/7lnNsjW/yr5WeIHJW58eoE6gWB1A7Yo3Ng6zR9s94Yj9v9PzVzJPD1aaU6hq3IJMzV6uU5PQp4mTuxuQ0yik5mdt/sIamb+steUnfjUJww=,iv:dc2GtnPRkXyOikjVi0teOlQ6FdSowf6/BjX21ukSdHc=,tag:hfDP2Bk95FK9kuR1fSc26w==,type:str] + database: + password: ENC[AES256_GCM,data:wLVLlRQYE+e4rQ==,iv:2foG2y5yg2goA2fNLKMw9ESLJ9vVmS61ozbn9StQEuM=,tag:6Tl46uxUdMe12pMbSQ5XGg==,type:str] +sops: + gcp_kms: + - resource_id: projects/ynet-stage/locations/global/keyRings/sops/cryptoKeys/sops-uat-key + created_at: "2026-04-02T12:14:10Z" + enc: CiQAMMK76XoOsaEq3elJXL/OGZ/OvdDWr3hiLdfb5oQzSqJDrqESSQC9etkIbM0LvrkQHLBIV6ENlIMxX5lz/YMxBp/J5pzkO6KG3TUQ20eQmBVovw0EI1lEox7J1GtLwENfBfcYFCy1us7df6/0JNI= + lastmodified: "2026-05-27T09:25:28Z" + mac: ENC[AES256_GCM,data:LrhABVJ46c8SGF5uRvk+WxfB0ocjaE11OOj+Yen4W8SJ3AtZnjdws5PFHseJSDUl9e0CkBXwiml+74WZ2A7YQpwzDTf3F2/R4W7a4yW49NaCsgj/0Nt14pGgVOQ0bdwcnJhZWiZbzsbHQ+5NmKfLQKG7SxavrjjnNljcaSNZIHc=,iv:DSgyBbMgGkBnJRotkETBCO35t2lO/BlpOQr48Q8Mqmk=,tag:xhD87+T6doRRBSmZ6tNKqw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2 diff --git a/clearnode/chart/templates/configmap.yaml b/nitronode/chart/templates/configmap.yaml similarity index 77% rename from clearnode/chart/templates/configmap.yaml rename to nitronode/chart/templates/configmap.yaml index ff5a9d6f5..1835eb2d9 100644 --- a/clearnode/chart/templates/configmap.yaml +++ b/nitronode/chart/templates/configmap.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "clearnode.common.fullname" . }}-config + name: {{ include "nitronode.common.fullname" . }}-config labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} annotations: "helm.sh/hook": pre-install, pre-upgrade "helm.sh/hook-delete-policy": before-hook-creation diff --git a/clearnode/chart/templates/secret.yaml b/nitronode/chart/templates/env-secret.yaml similarity index 50% rename from clearnode/chart/templates/secret.yaml rename to nitronode/chart/templates/env-secret.yaml index de37a844a..fb67d3be4 100644 --- a/clearnode/chart/templates/secret.yaml +++ b/nitronode/chart/templates/env-secret.yaml @@ -2,21 +2,16 @@ apiVersion: v1 kind: Secret metadata: - name: {{ include "clearnode.common.fullname" . }}-secret-env + name: {{ include "nitronode.common.fullname" . }}-secret-env labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} annotations: "helm.sh/hook": pre-install, pre-upgrade "helm.sh/hook-delete-policy": before-hook-creation "helm.sh/hook-weight": "-1" data: {{- with .Values.config.database }} -{{- if eq .driver "postgres" }} - CLEARNODE_DATABASE_URL: {{ printf "postgres://%s:%s@%s:%s/%s?sslmode=%s" .user .password .host (print .port) .name .sslmode | b64enc }} -{{- end }} -{{- if eq .driver "sqlite" }} - CLEARNODE_DATABASE_URL: {{ printf "file:%s" .path | b64enc }} -{{- end }} + NITRONODE_DATABASE_PASSWORD: {{ .password | b64enc }} {{- end }} {{- range $key, $value := .Values.config.secretEnvs }} {{- $key | nindent 2 }}: {{ $value | print | b64enc }} diff --git a/clearnode/chart/templates/full-deployment.yaml b/nitronode/chart/templates/full-deployment.yaml similarity index 58% rename from clearnode/chart/templates/full-deployment.yaml rename to nitronode/chart/templates/full-deployment.yaml index 26dae6772..6fd8efb53 100644 --- a/clearnode/chart/templates/full-deployment.yaml +++ b/nitronode/chart/templates/full-deployment.yaml @@ -1,12 +1,12 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} app.kubernetes.io/component: server spec: - {{- include "clearnode.component.replicaCount" . | nindent 2 }} + {{- include "nitronode.component.replicaCount" . | nindent 2 }} strategy: type: RollingUpdate rollingUpdate: @@ -14,30 +14,30 @@ spec: maxUnavailable: 0 selector: matchLabels: - {{- include "clearnode.common.selectorLabels" . | nindent 6 }} + {{- include "nitronode.common.selectorLabels" . | nindent 6 }} app.kubernetes.io/component: server template: metadata: annotations: - {{- include "clearnode.component.metricsAnnotations" .Values.metrics | nindent 8 }} + {{- include "nitronode.component.metricsAnnotations" .Values.metrics | nindent 8 }} {{- if .Values.config.secretEnvs }} - checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/env-secret.yaml") . | sha256sum }} {{- end }} checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} labels: - {{- include "clearnode.common.selectorLabels" . | nindent 8 }} + {{- include "nitronode.common.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: server spec: - {{- with .Values.serviceAccount }} - serviceAccountName: {{ . }} + {{- if .Values.serviceAccount.create }} + serviceAccountName: {{ include "nitronode.common.fullname" . }} {{- end }} containers: - name: api args: {{ .Values.config.args | toYaml | nindent 10 }} - image: {{ include "clearnode.component.image" .Values.image }} + image: {{ include "nitronode.component.image" .Values.image }} imagePullPolicy: IfNotPresent env: - {{- include "clearnode.common.env" . | nindent 12 }} + {{- include "nitronode.common.env" . | nindent 12 }} {{- if or .Values.config.secretEnvs .Values.config.envSecret }} envFrom: {{- if .Values.config.envSecret }} @@ -46,7 +46,7 @@ spec: {{- end }} {{- if .Values.config.secretEnvs }} - secretRef: - name: {{ include "clearnode.common.fullname" . }}-secret-env + name: {{ include "nitronode.common.fullname" . }}-secret-env {{- end }} {{- end }} volumeMounts: @@ -57,19 +57,19 @@ spec: mountPath: /etc/gcp readOnly: true {{- end }} - {{- include "clearnode.component.ports" .Values.service | nindent 10 }} - {{- include "clearnode.component.resources" .Values.resources | nindent 10 }} - {{- include "clearnode.component.probes" . | nindent 10 }} + {{- include "nitronode.component.ports" .Values.service | nindent 10 }} + {{- include "nitronode.component.resources" .Values.resources | nindent 10 }} + {{- include "nitronode.component.probes" . | nindent 10 }} volumes: - name: config configMap: - name: {{ include "clearnode.common.fullname" . }}-config + name: {{ include "nitronode.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 }} + {{- include "nitronode.common.imagePullSecrets" . | nindent 6 }} + {{- include "nitronode.common.nodeSelectorLabels" . | nindent 6 }} + {{- include "nitronode.common.affinity" . | nindent 6 }} + {{- include "nitronode.common.tolerations" . | nindent 6 }} diff --git a/clearnode/chart/templates/gateway.yaml b/nitronode/chart/templates/gateway.yaml similarity index 89% rename from clearnode/chart/templates/gateway.yaml rename to nitronode/chart/templates/gateway.yaml index 61d7132b4..502d37c86 100644 --- a/clearnode/chart/templates/gateway.yaml +++ b/nitronode/chart/templates/gateway.yaml @@ -2,9 +2,9 @@ apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} annotations: cert-manager.io/cluster-issuer: {{ .Values.networking.tlsClusterIssuer }} # FIXME: This annotation doesn't work diff --git a/clearnode/chart/templates/helpers/_common.tpl b/nitronode/chart/templates/helpers/_common.tpl similarity index 65% rename from clearnode/chart/templates/helpers/_common.tpl rename to nitronode/chart/templates/helpers/_common.tpl index eeed535a2..ed39a50db 100644 --- a/clearnode/chart/templates/helpers/_common.tpl +++ b/nitronode/chart/templates/helpers/_common.tpl @@ -3,7 +3,7 @@ {{/* Expand the name of the component. */}} -{{- define "clearnode.common.name" -}} +{{- define "nitronode.common.name" -}} {{- .Chart.Name | trunc 63 | trimSuffix "-" }} {{- end }} @@ -11,7 +11,7 @@ Expand the name of the component. Create a default fully common name. If release name contains chart name it will be used as a full name. */}} -{{- define "clearnode.common.fullname" -}} +{{- define "nitronode.common.fullname" -}} {{- if .Values.prefixOverride }} {{- printf "%s-%s" .Values.prefixOverride .Chart.Name | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,7 +26,7 @@ If release name contains chart name it will be used as a full name. {{/* Common Selector labels */}} -{{- define "clearnode.common.selectorLabels" -}} +{{- define "nitronode.common.selectorLabels" -}} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} @@ -34,9 +34,9 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{/* Common labels */}} -{{- define "clearnode.common.labels" -}} -helm.sh/chart: {{ include "clearnode.common.chart" . }} -{{ include "clearnode.common.selectorLabels" . }} +{{- define "nitronode.common.labels" -}} +helm.sh/chart: {{ include "nitronode.common.chart" . }} +{{ include "nitronode.common.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -49,39 +49,46 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Create chart name and version as used by the chart label. */}} -{{- define "clearnode.common.chart" -}} +{{- define "nitronode.common.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Returns common image pull secrets */}} -{{- define "clearnode.common.imagePullSecrets" -}} -{{- with .Values.imagePullSecret }} -imagePullSecrets: -- name: {{ . }} +{{- define "nitronode.common.imagePullSecrets" -}} +{{- if or .Values.imagePullSecret .Values.ghcrPullDockerConfigJson }} +imagePullSecrets: +{{- if .Values.imagePullSecret }} +- name: {{ .Values.imagePullSecret }} +{{- end }} +{{- if .Values.ghcrPullDockerConfigJson }} +- name: {{ include "nitronode.common.fullname" . }}-ghcr-pull +{{- end }} {{- end }} {{- end }} {{/* Returns common environment variables */}} -{{- define "clearnode.common.env" -}} -- name: CLEARNODE_LOG_LEVEL +{{- define "nitronode.common.env" -}} +- name: NITRONODE_LOG_LEVEL value: {{ .Values.config.logLevel }} -- name: CLEARNODE_CONFIG_DIR_PATH +- name: NITRONODE_CONFIG_DIR_PATH value: /app/config {{- with .Values.config.database }} -- name: CLEARNODE_DATABASE_DRIVER +- name: NITRONODE_DATABASE_DRIVER value: {{ .driver }} -- name: CLEARNODE_DATABASE_NAME +- name: NITRONODE_DATABASE_NAME value: {{ .name }} -- name: CLEARNODE_DATABASE_HOST +- name: NITRONODE_DATABASE_HOST value: {{ .host }} -- name: CLEARNODE_DATABASE_PORT +- name: NITRONODE_DATABASE_PORT value: "{{ print .port }}" -- name: CLEARNODE_DATABASE_USERNAME +- name: NITRONODE_DATABASE_USERNAME value: {{ .user }} +- name: NITRONODE_DATABASE_SSLMODE + value: {{ .sslmode | default "require" | quote }} {{- end }} {{- range $key, $value := .Values.config.extraEnvs }} - name: {{ $key | upper }} @@ -96,7 +103,7 @@ Returns common environment variables {{/* Returns common node selector labels */}} -{{- define "clearnode.common.nodeSelectorLabels" -}} +{{- define "nitronode.common.nodeSelectorLabels" -}} {{- with .Values.nodeSelector }} nodeSelector: {{ toYaml . | nindent 2 }} @@ -106,7 +113,7 @@ nodeSelector: {{/* Returns common tolerations */}} -{{- define "clearnode.common.tolerations" -}} +{{- define "nitronode.common.tolerations" -}} {{- with .Values.tolerations }} tolerations: {{ toYaml . }} @@ -116,7 +123,7 @@ tolerations: {{/* Returns common pod's affinity */}} -{{- define "clearnode.common.affinity" -}} +{{- define "nitronode.common.affinity" -}} {{- with .Values.affinity }} affinity: {{ toYaml . | nindent 2 }} diff --git a/clearnode/chart/templates/helpers/_component.tpl b/nitronode/chart/templates/helpers/_component.tpl similarity index 86% rename from clearnode/chart/templates/helpers/_component.tpl rename to nitronode/chart/templates/helpers/_component.tpl index 7fcdad52b..a9ee236f7 100644 --- a/clearnode/chart/templates/helpers/_component.tpl +++ b/nitronode/chart/templates/helpers/_component.tpl @@ -3,7 +3,7 @@ {{/* Returns Prometheus metrics' annotations depending on input Values */}} -{{- define "clearnode.component.metricsAnnotations" -}} +{{- define "nitronode.component.metricsAnnotations" -}} prometheus.io/scrape: {{ default false .enabled | print | quote }} prometheus.io/port: {{ default "4242" .port | print | quote }} prometheus.io/path: {{ default "/metrics" .endpoint | print | quote }} @@ -12,7 +12,7 @@ prometheus.io/path: {{ default "/metrics" .endpoint | print | quote }} {{/* Returns replica count depending on component and HPA settings */}} -{{- define "clearnode.component.replicaCount" -}} +{{- define "nitronode.component.replicaCount" -}} {{- if not (and .autoscaling .autoscaling.enabled) }} replicas: {{ .replicaCount }} {{- end }} @@ -21,14 +21,14 @@ replicas: {{ .replicaCount }} {{/* Returns full docker image name */}} -{{- define "clearnode.component.image" -}} +{{- define "nitronode.component.image" -}} {{ printf "%s:%s" (print .repository) (print .tag) }} {{- end }} {{/* Returns container ports configuration depending on input service */}} -{{- define "clearnode.component.ports" -}} +{{- define "nitronode.component.ports" -}} {{- if .http.enabled }} ports: {{- with .http }} @@ -42,7 +42,7 @@ ports: {{/* Returns component's resource consumption */}} -{{- define "clearnode.component.resources" -}} +{{- define "nitronode.component.resources" -}} resources: requests: cpu: {{ default "100m" .requests.cpu }} @@ -57,7 +57,7 @@ resources: {{/* Returns component's probes */}} -{{- define "clearnode.component.probes" -}} +{{- define "nitronode.component.probes" -}} {{- $port := default .Values.service.http.port .Values.service.http.internalPort }} {{- range $name, $probe := .Values.probes }} {{- if $probe.enabled }} diff --git a/clearnode/chart/templates/helpers/_hpa.tpl b/nitronode/chart/templates/helpers/_hpa.tpl similarity index 86% rename from clearnode/chart/templates/helpers/_hpa.tpl rename to nitronode/chart/templates/helpers/_hpa.tpl index b6b18b7d1..0dd42be91 100644 --- a/clearnode/chart/templates/helpers/_hpa.tpl +++ b/nitronode/chart/templates/helpers/_hpa.tpl @@ -2,7 +2,7 @@ {{/* Returns HorizontalPodAutoscaler API version depending on K8s cluster version */}} -{{- define "clearnode.hpa.apiVersion" -}} +{{- define "nitronode.hpa.apiVersion" -}} {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.Version -}} autoscaling/v2 {{- else -}} @@ -13,7 +13,7 @@ autoscaling/v2beta2 {{/* Returns HorizontalPodAutoscaler resource target utilization depending on K8s cluster version */}} -{{- define "clearnode.hpa.targetUtilization" -}} +{{- define "nitronode.hpa.targetUtilization" -}} {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.Version -}} target: type: Utilization diff --git a/clearnode/chart/templates/helpers/_ingress.tpl b/nitronode/chart/templates/helpers/_ingress.tpl similarity index 88% rename from clearnode/chart/templates/helpers/_ingress.tpl rename to nitronode/chart/templates/helpers/_ingress.tpl index 3fb3d2ede..8a2d32f07 100644 --- a/clearnode/chart/templates/helpers/_ingress.tpl +++ b/nitronode/chart/templates/helpers/_ingress.tpl @@ -3,7 +3,7 @@ {{/* Returns Ingress API version depending on K8s cluster version */}} -{{- define "clearnode.ingress.apiVersion" -}} +{{- define "nitronode.ingress.apiVersion" -}} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.Version -}} networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.Version -}} @@ -16,7 +16,7 @@ extensions/v1beta1 {{/* Returns default Ingress annotations */}} -{{- define "clearnode.ingress.annotations" -}} +{{- define "nitronode.ingress.annotations" -}} kubernetes.io/ingress.class: {{ default "nginx" .Values.networking.ingress.className }} {{- if .Values.networking.ingress.tls.enabled }} kubernetes.io/tls-acme: "true" @@ -35,7 +35,7 @@ nginx.ingress.kubernetes.io/rewrite-target: /ws {{/* Returns Ingress TLS configuration */}} -{{- define "clearnode.ingress.tls" -}} +{{- define "nitronode.ingress.tls" -}} {{- if .Values.networking.ingress.tls.enabled }} tls: - secretName: "{{ .Values.networking.externalHostname | replace "." "-" }}-tls" @@ -47,14 +47,14 @@ tls: {{/* Returns Ingress host path configuration */}} -{{- define "clearnode.ingress.httpPath" -}} +{{- define "nitronode.ingress.httpPath" -}} {{- $http := .Values.service.http }} - path: {{ $http.path }} {{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.Version }} pathType: Prefix {{- end }} backend: - {{ $svcName := include "clearnode.common.fullname" . }} + {{ $svcName := include "nitronode.common.fullname" . }} {{ $svcPort := default $http.port $http.internalPort }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.Version }} service: diff --git a/clearnode/chart/templates/http-route-redirect.yaml b/nitronode/chart/templates/http-route-redirect.yaml similarity index 75% rename from clearnode/chart/templates/http-route-redirect.yaml rename to nitronode/chart/templates/http-route-redirect.yaml index bd649c0c6..69067d2e8 100644 --- a/clearnode/chart/templates/http-route-redirect.yaml +++ b/nitronode/chart/templates/http-route-redirect.yaml @@ -2,15 +2,15 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ printf "%s-http-redirect" (include "clearnode.common.fullname" .) }} + name: {{ printf "%s-http-redirect" (include "nitronode.common.fullname" .) }} labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway namespace: {{ .Release.Namespace }} - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} port: 80 sectionName: http hostnames: diff --git a/clearnode/chart/templates/http-route.yaml b/nitronode/chart/templates/http-route.yaml similarity index 72% rename from clearnode/chart/templates/http-route.yaml rename to nitronode/chart/templates/http-route.yaml index 33cd2c669..cbdecc7dd 100644 --- a/clearnode/chart/templates/http-route.yaml +++ b/nitronode/chart/templates/http-route.yaml @@ -3,15 +3,15 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway namespace: {{ .Release.Namespace }} - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} port: 443 sectionName: https hostnames: @@ -22,6 +22,6 @@ spec: type: PathPrefix value: {{ $http.path }} backendRefs: - - name: {{ include "clearnode.common.fullname" . }} + - name: {{ include "nitronode.common.fullname" . }} port: {{ default $http.port $http.internalPort }} {{- end }} diff --git a/nitronode/chart/templates/image-pull-secret.yaml b/nitronode/chart/templates/image-pull-secret.yaml new file mode 100644 index 000000000..81cac216f --- /dev/null +++ b/nitronode/chart/templates/image-pull-secret.yaml @@ -0,0 +1,11 @@ +{{- if .Values.ghcrPullDockerConfigJson }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "nitronode.common.fullname" . }}-ghcr-pull + labels: + {{- include "nitronode.common.labels" . | nindent 4 }} +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ .Values.ghcrPullDockerConfigJson | quote }} +{{- end }} diff --git a/nitronode/chart/templates/ingress.yaml b/nitronode/chart/templates/ingress.yaml new file mode 100644 index 000000000..773175d77 --- /dev/null +++ b/nitronode/chart/templates/ingress.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.networking.ingress.enabled .Values.service.http.enabled }} +apiVersion: {{ include "nitronode.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "nitronode.common.fullname" . }} + labels: + {{- include "nitronode.common.labels" . | nindent 4 }} + annotations: + {{- include "nitronode.ingress.annotations" . | nindent 4 }} +spec: + rules: + - host: {{ .Values.networking.externalHostname }} + http: + paths: + {{- include "nitronode.ingress.httpPath" . | nindent 10 }} + {{- include "nitronode.ingress.tls" . | nindent 2 }} +{{- end }} diff --git a/clearnode/chart/templates/podmonitoring.yaml b/nitronode/chart/templates/podmonitoring.yaml similarity index 66% rename from clearnode/chart/templates/podmonitoring.yaml rename to nitronode/chart/templates/podmonitoring.yaml index 38a3a6383..7b7655a3b 100644 --- a/clearnode/chart/templates/podmonitoring.yaml +++ b/nitronode/chart/templates/podmonitoring.yaml @@ -2,13 +2,13 @@ apiVersion: monitoring.googleapis.com/v1 kind: PodMonitoring metadata: - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} spec: selector: matchLabels: - {{- include "clearnode.common.selectorLabels" . | nindent 6 }} + {{- include "nitronode.common.selectorLabels" . | nindent 6 }} endpoints: - port: {{ .Values.metrics.port }} path: {{ .Values.metrics.endpoint }} diff --git a/nitronode/chart/templates/service-account.yaml b/nitronode/chart/templates/service-account.yaml new file mode 100644 index 000000000..40970bbe8 --- /dev/null +++ b/nitronode/chart/templates/service-account.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nitronode.common.fullname" . }} + labels: + {{- include "nitronode.common.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/clearnode/chart/templates/service.yaml b/nitronode/chart/templates/service.yaml similarity index 68% rename from clearnode/chart/templates/service.yaml rename to nitronode/chart/templates/service.yaml index 9d964d98d..24a2b12f0 100644 --- a/clearnode/chart/templates/service.yaml +++ b/nitronode/chart/templates/service.yaml @@ -3,9 +3,9 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "clearnode.common.fullname" . }} + name: {{ include "nitronode.common.fullname" . }} labels: - {{- include "clearnode.common.labels" . | nindent 4 }} + {{- include "nitronode.common.labels" . | nindent 4 }} spec: type: ClusterIP ports: @@ -16,6 +16,6 @@ spec: protocol: TCP {{- end }} selector: - {{- include "clearnode.common.selectorLabels" . | nindent 4 }} + {{- include "nitronode.common.selectorLabels" . | nindent 4 }} app.kubernetes.io/component: server {{- end }} diff --git a/clearnode/chart/templates/tests/stress-test.yaml b/nitronode/chart/templates/tests/stress-test.yaml similarity index 87% rename from clearnode/chart/templates/tests/stress-test.yaml rename to nitronode/chart/templates/tests/stress-test.yaml index 90e77e03b..e328d7c14 100644 --- a/clearnode/chart/templates/tests/stress-test.yaml +++ b/nitronode/chart/templates/tests/stress-test.yaml @@ -1,5 +1,5 @@ {{- if .Values.stressTest.enabled }} -{{- $fullname := include "clearnode.common.fullname" . -}} +{{- $fullname := include "nitronode.common.fullname" . -}} {{- $defaults := .Values.stressTest -}} {{- $defaultWsURL := $defaults.wsURL | default (printf "ws://%s:%d/ws" $fullname (.Values.service.http.port | int)) -}} {{- $podCount := len .Values.stressTest.pods -}} @@ -19,7 +19,7 @@ kind: ConfigMap metadata: name: {{ $fullname }}-stress-config labels: - {{- include "clearnode.common.labels" $ | nindent 4 }} + {{- include "nitronode.common.labels" $ | nindent 4 }} annotations: "helm.sh/hook": test "helm.sh/hook-delete-policy": before-hook-creation @@ -44,7 +44,7 @@ kind: Secret metadata: name: {{ $fullname }}-stress-secret labels: - {{- include "clearnode.common.labels" $ | nindent 4 }} + {{- include "nitronode.common.labels" $ | nindent 4 }} annotations: "helm.sh/hook": test "helm.sh/hook-delete-policy": before-hook-creation @@ -66,7 +66,7 @@ kind: Job metadata: name: {{ $fullname }}-stress-test labels: - {{- include "clearnode.common.labels" $ | nindent 4 }} + {{- include "nitronode.common.labels" $ | nindent 4 }} app.kubernetes.io/component: stress-test annotations: "helm.sh/hook": test @@ -91,7 +91,7 @@ spec: restartPolicy: Never containers: - name: stress-test - image: {{ include "clearnode.component.image" $.Values.image }} + image: {{ include "nitronode.component.image" $.Values.image }} command: ["sh", "-c"] args: - | @@ -105,7 +105,7 @@ spec: export STRESS_PRIVATE_KEY="$(cat /stress-secrets/private-key)" fi {{- end }} - exec clearnode stress-test $STRESS_SPECS + exec nitronode stress-test basic $STRESS_SPECS env: - name: JOB_COMPLETION_INDEX valueFrom: @@ -129,7 +129,7 @@ spec: secret: secretName: {{ $fullname }}-stress-secret {{- end }} - {{- include "clearnode.common.imagePullSecrets" $ | nindent 6 }} - {{- include "clearnode.common.nodeSelectorLabels" $ | nindent 6 }} - {{- include "clearnode.common.tolerations" $ | nindent 6 }} + {{- include "nitronode.common.imagePullSecrets" $ | nindent 6 }} + {{- include "nitronode.common.nodeSelectorLabels" $ | nindent 6 }} + {{- include "nitronode.common.tolerations" $ | nindent 6 }} {{- end }} diff --git a/clearnode/chart/values.yaml b/nitronode/chart/values.yaml similarity index 81% rename from clearnode/chart/values.yaml rename to nitronode/chart/values.yaml index 4ba7a8b51..c8e85e5bd 100644 --- a/clearnode/chart/values.yaml +++ b/nitronode/chart/values.yaml @@ -3,7 +3,7 @@ fullnameOverride: "" config: # -- List of arguments to pass to the container - args: ["clearnode"] + args: ["nitronode"] # -- Log level (info, debug, warn, error) logLevel: info database: @@ -16,13 +16,18 @@ config: # -- Database port port: 5432 # -- Database name - name: clearnode + name: nitronode # -- Database user user: changeme # -- Database password password: changeme - # -- Database SSL mode (disable, require, verify-ca, verify-full) - sslmode: disable + # -- Database SSL mode (disable, require, verify-ca, verify-full). + # Defaults to `require` so any deployment that exposes Postgres over an + # untrusted network gets TLS without extra configuration. Override to + # `disable` for setups where the database is only reachable on a private + # network (e.g. a cluster-internal pgbouncer / VPC-only Cloud SQL) and TLS + # is not required; use `verify-ca` / `verify-full` for strict cert checking. + sslmode: require # -- Name of the secret containing GCP SA Credentials (Optional) gcpSaSecret: "" # -- Additional environment variables as key-value pairs @@ -45,7 +50,7 @@ replicaCount: 1 image: # -- Docker image repository - repository: ghcr.io/layer-3/nitrolite/clearnode + repository: ghcr.io/layer-3/nitrolite/nitronode # -- Docker image tag tag: v1.0.0-rc.0 @@ -95,8 +100,12 @@ resources: # memory: 256Mi # ephemeral-storage: 100Mi -# -- Service account name -serviceAccount: "" +serviceAccount: + # -- Create a ServiceAccount resource + create: false + # -- Annotations to add to the ServiceAccount (e.g. for GKE Workload Identity) + annotations: {} + # iam.gke.io/gcp-service-account: my-sa@my-project.iam.gserviceaccount.com autoscaling: # -- Enable autoscaling @@ -114,7 +123,7 @@ networking: # -- TLS cluster issuer tlsClusterIssuer: zerossl-prod # -- External hostname for the gateway - externalHostname: clearnode.example.com + externalHostname: nitronode.example.com gateway: # -- Enable API gateway @@ -140,6 +149,9 @@ networking: # -- Image pull secret name imagePullSecret: "" +# -- Base64-encoded .dockerconfigjson for GHCR pull secret (provided via SOPS-encrypted secrets) +ghcrPullDockerConfigJson: "" + # -- Node selector nodeSelector: {} @@ -187,7 +199,7 @@ stressTest: # - name: transfers # specs: # - "transfer-roundtrip:10:4:usdc" - # wsURL: "wss://external-clearnode.example.com/ws" + # wsURL: "wss://external-nitronode.example.com/ws" # privateKey: "" # connections: 20 # timeout: "30m" diff --git a/clearnode/config/migrations/postgres/20251222000000_initial_schema.sql b/nitronode/config/migrations/postgres/20251222000000_initial_schema.sql similarity index 99% rename from clearnode/config/migrations/postgres/20251222000000_initial_schema.sql rename to nitronode/config/migrations/postgres/20251222000000_initial_schema.sql index fd7ba50a3..d26e6cca0 100644 --- a/clearnode/config/migrations/postgres/20251222000000_initial_schema.sql +++ b/nitronode/config/migrations/postgres/20251222000000_initial_schema.sql @@ -12,7 +12,7 @@ CREATE TABLE channels ( challenge_expires_at TIMESTAMPTZ, nonce NUMERIC(20,0) NOT NULL DEFAULT 0, approved_sig_validators VARCHAR(66) NOT NULL DEFAULT 0, - status SMALLINT NOT NULL, -- ChannelStatus enum: 0=void, 1=open, 2=challenged, 3=closed + status SMALLINT NOT NULL, -- ChannelStatus enum: 0=void, 1=open, 2=challenged, 3=closing, 4=closed state_version NUMERIC(20,0) NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() diff --git a/nitronode/config/migrations/postgres/20260409000000_add_home_blockchain_id_to_user_balances.sql b/nitronode/config/migrations/postgres/20260409000000_add_home_blockchain_id_to_user_balances.sql new file mode 100644 index 000000000..a09ee4121 --- /dev/null +++ b/nitronode/config/migrations/postgres/20260409000000_add_home_blockchain_id_to_user_balances.sql @@ -0,0 +1,36 @@ +-- +goose Up +ALTER TABLE user_balances ADD COLUMN home_blockchain_id NUMERIC(20,0) NOT NULL DEFAULT 0; +ALTER TABLE user_balances ADD COLUMN enforced NUMERIC(78, 18) NOT NULL DEFAULT 0; + +UPDATE user_balances ub +SET home_blockchain_id = c.blockchain_id +FROM ( + SELECT DISTINCT ON (user_wallet, asset) + user_wallet, asset, blockchain_id, channel_id, state_version + FROM channels + WHERE type = 1 AND status <= 1 + ORDER BY user_wallet, asset, state_version DESC, channel_id DESC +) c +WHERE c.user_wallet = ub.user_wallet + AND c.asset = ub.asset; + +UPDATE user_balances ub +SET enforced = COALESCE(( + SELECT s.home_user_balance + FROM ( + SELECT DISTINCT ON (user_wallet, asset) + user_wallet, asset, channel_id, state_version + FROM channels + WHERE type = 1 AND status <= 1 AND state_version > 0 + ORDER BY user_wallet, asset, state_version DESC, channel_id DESC + ) c + JOIN channel_states s ON s.home_channel_id = c.channel_id AND s.version = c.state_version + WHERE c.user_wallet = ub.user_wallet + AND c.asset = ub.asset + ORDER BY s.epoch DESC + LIMIT 1 +), 0); + +-- +goose Down +ALTER TABLE user_balances DROP COLUMN enforced; +ALTER TABLE user_balances DROP COLUMN home_blockchain_id; diff --git a/nitronode/config/migrations/postgres/20260420000000_add_application_id_to_writes.sql b/nitronode/config/migrations/postgres/20260420000000_add_application_id_to_writes.sql new file mode 100644 index 000000000..b2f48b0c8 --- /dev/null +++ b/nitronode/config/migrations/postgres/20260420000000_add_application_id_to_writes.sql @@ -0,0 +1,12 @@ +-- +goose Up +ALTER TABLE channel_states ADD COLUMN application_id VARCHAR(66); +ALTER TABLE transactions ADD COLUMN application_id VARCHAR(66); + +CREATE INDEX idx_channel_states_app_id ON channel_states(application_id); +CREATE INDEX idx_transactions_app_id ON transactions(application_id); + +-- +goose Down +DROP INDEX IF EXISTS idx_transactions_app_id; +DROP INDEX IF EXISTS idx_channel_states_app_id; +ALTER TABLE transactions DROP COLUMN application_id; +ALTER TABLE channel_states DROP COLUMN application_id; diff --git a/nitronode/config/migrations/postgres/20260507000000_add_current_session_key_states.sql b/nitronode/config/migrations/postgres/20260507000000_add_current_session_key_states.sql new file mode 100644 index 000000000..d121e12f0 --- /dev/null +++ b/nitronode/config/migrations/postgres/20260507000000_add_current_session_key_states.sql @@ -0,0 +1,39 @@ +-- +goose Up +-- Pointer table holding the latest version per (user_address, session_key, kind). +-- Reads of the get_last_key_states endpoints filter this table by user_address (+ optional +-- session_key) and JOIN the corresponding history table on (user_address, session_key, version). +-- This eliminates the GROUP BY scan over history that grows with version churn and bounds +-- per-request DB work to O(distinct keys for user, kind). +-- +-- kind values (SessionKeyKind enum on the Go side): +-- 1 = channel +-- 2 = app_session +CREATE TABLE current_session_key_states_v1 ( + user_address CHAR(42) NOT NULL, + session_key CHAR(42) NOT NULL, + kind SMALLINT NOT NULL, + version NUMERIC(20,0) NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (user_address, session_key, kind) +); + +CREATE INDEX idx_current_session_key_states_v1_user_kind + ON current_session_key_states_v1(user_address, kind); + +-- Backfill from app session key history: latest version per (user_address, session_key). +INSERT INTO current_session_key_states_v1 (user_address, session_key, kind, version, updated_at) +SELECT user_address, session_key, 2, MAX(version), NOW() +FROM app_session_key_states_v1 +GROUP BY user_address, session_key +ON CONFLICT (user_address, session_key, kind) DO NOTHING; + +-- Backfill from channel session key history: latest version per (user_address, session_key). +INSERT INTO current_session_key_states_v1 (user_address, session_key, kind, version, updated_at) +SELECT user_address, session_key, 1, MAX(version), NOW() +FROM channel_session_key_states_v1 +GROUP BY user_address, session_key +ON CONFLICT (user_address, session_key, kind) DO NOTHING; + +-- +goose Down +DROP INDEX IF EXISTS idx_current_session_key_states_v1_user_kind; +DROP TABLE IF EXISTS current_session_key_states_v1; diff --git a/nitronode/config/migrations/postgres/20260508000000_session_key_ownership_constraints.sql b/nitronode/config/migrations/postgres/20260508000000_session_key_ownership_constraints.sql new file mode 100644 index 000000000..4e40d42ba --- /dev/null +++ b/nitronode/config/migrations/postgres/20260508000000_session_key_ownership_constraints.sql @@ -0,0 +1,66 @@ +-- +goose Up +-- Bind a session_key to a single owner (per kind) and require co-signature at submit time. + +-- Co-signature: the session-key holder proves possession at registration and on every update. +-- Nullable to accommodate rows written before this column existed; new submits enforce non-null +-- in application code. Columns add first so a constraint failure below does not leave the +-- session_key_sig schema partially applied. +ALTER TABLE app_session_key_states_v1 + ADD COLUMN session_key_sig TEXT; + +ALTER TABLE channel_session_key_states_v1 + ADD COLUMN session_key_sig TEXT; + +-- Pre-flight: refuse the migration if duplicate (session_key, kind) rows are present in +-- current_session_key_states_v1. Such rows are evidence of cross-wallet collisions that +-- the old code path allowed; manual remediation is required before the constraint adds. +-- +goose StatementBegin +DO $$ +DECLARE + dup_count bigint; +BEGIN + SELECT COUNT(*) INTO dup_count + FROM ( + SELECT session_key, kind + FROM current_session_key_states_v1 + GROUP BY session_key, kind + HAVING COUNT(*) > 1 + ) AS dups; + + IF dup_count > 0 THEN + RAISE EXCEPTION 'duplicate (session_key, kind) rows detected (%); manual remediation required before applying constraint', dup_count; + END IF; +END $$; +-- +goose StatementEnd + +ALTER TABLE current_session_key_states_v1 + ADD CONSTRAINT current_session_key_states_v1_key_kind_uniq UNIQUE (session_key, kind); + +-- Fail closed at the DB layer for new history rows during rolling deploys: a pre-MF-H02 binary +-- (already running the MF-H01 schema where current_session_key_states_v1 exists) would happily +-- insert history rows without session_key_sig, and the new GetAppSessionKeyOwner/GetChannelSessionKeyOwner +-- lookups would then trust those unproven rows as legitimate owners. NOT VALID skips the legacy +-- backfill scan so pre-existing rows are not blocked; only future inserts are checked. +ALTER TABLE app_session_key_states_v1 + ADD CONSTRAINT app_session_key_states_v1_session_key_sig_present_chk + CHECK (session_key_sig IS NOT NULL AND session_key_sig <> '') NOT VALID; + +ALTER TABLE channel_session_key_states_v1 + ADD CONSTRAINT channel_session_key_states_v1_session_key_sig_present_chk + CHECK (session_key_sig IS NOT NULL AND session_key_sig <> '') NOT VALID; + +-- +goose Down +ALTER TABLE channel_session_key_states_v1 + DROP CONSTRAINT IF EXISTS channel_session_key_states_v1_session_key_sig_present_chk; + +ALTER TABLE app_session_key_states_v1 + DROP CONSTRAINT IF EXISTS app_session_key_states_v1_session_key_sig_present_chk; + +ALTER TABLE current_session_key_states_v1 + DROP CONSTRAINT IF EXISTS current_session_key_states_v1_key_kind_uniq; + +ALTER TABLE channel_session_key_states_v1 + DROP COLUMN IF EXISTS session_key_sig; + +ALTER TABLE app_session_key_states_v1 + DROP COLUMN IF EXISTS session_key_sig; diff --git a/nitronode/config/migrations/postgres/20260513000000_add_channel_status_closing.sql b/nitronode/config/migrations/postgres/20260513000000_add_channel_status_closing.sql new file mode 100644 index 000000000..438e90159 --- /dev/null +++ b/nitronode/config/migrations/postgres/20260513000000_add_channel_status_closing.sql @@ -0,0 +1,20 @@ +-- +goose Up + +-- Introduce ChannelStatusClosing (3) between Challenged (2) and Closed (4). +-- Prior to this migration Closed was encoded as 3; shift it to 4 first, +-- then the gap at 3 becomes the new Closing value. +-- +-- Status enum mapping after this migration: +-- 0 = void +-- 1 = open +-- 2 = challenged +-- 3 = closing (co-signed Finalize stored off-chain; on-chain close pending) +-- 4 = closed + +UPDATE channels SET status = 4 WHERE status = 3; + +-- +goose Down + +UPDATE channels SET status = 3 WHERE status = 4; +-- Note: any rows with status = 3 (Closing) at rollback time are left as-is; +-- they have no valid representation in the old schema and must be resolved manually. diff --git a/clearnode/config/schemas/action_gateway_schema.yaml b/nitronode/config/schemas/action_gateway_schema.yaml similarity index 100% rename from clearnode/config/schemas/action_gateway_schema.yaml rename to nitronode/config/schemas/action_gateway_schema.yaml diff --git a/nitronode/event_handlers/service.go b/nitronode/event_handlers/service.go new file mode 100644 index 000000000..732a76621 --- /dev/null +++ b/nitronode/event_handlers/service.go @@ -0,0 +1,867 @@ +package event_handlers + +import ( + "context" + "fmt" + "time" + + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" +) + +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). +type EventHandlerService struct { + nodeSigner *core.ChannelDefaultSigner + statePacker core.StatePacker +} + +// NewEventHandlerService creates a new EventHandlerService instance. +// nodeSigner and statePacker are used to backfill the node signature on the +// checkpointed head state when it is missing from the local record. +func NewEventHandlerService(nodeSigner *core.ChannelDefaultSigner, statePacker core.StatePacker) *EventHandlerService { + return &EventHandlerService{ + nodeSigner: nodeSigner, + statePacker: statePacker, + } +} + +// HandleNodeBalanceUpdated processes the NodeBalanceUpdated event emitted when the node's +// on-chain liquidity changes. It records the new node liquidity for the (blockchain, asset) +// pair via SetNodeBalance; this is observability data only and does not affect user staking +// state. +func (s *EventHandlerService) HandleNodeBalanceUpdated(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.NodeBalanceUpdatedEvent) error { + logger := log.FromContext(ctx) + + if err := tx.SetNodeBalance(event.BlockchainID, event.Asset, event.Balance); err != nil { + return err + } + + logger.Info("handled NodeBalanceUpdated event", "blockchainID", event.BlockchainID, "asset", event.Asset, "balance", event.Balance) + return nil +} + +// HandleHomeChannelCreated processes the HomeChannelCreated event emitted when a home channel +// is successfully created on-chain. It updates the channel status to Open and sets the state version. +// The channel must exist in the database with type ChannelTypeHome, otherwise a warning is logged. +// A legitimate Created event is observed exactly once per channel, when the local row is still in +// the ChannelStatusVoid state seeded by CreateChannel. If the channel has already advanced past +// Void, this handler is being re-fired (indexer replay, chain reorg, block reprocessing) and any +// mutation here would regress the post-Open lifecycle — most importantly resetting a Closing +// channel back to Open and erasing the Finalize marker that gates CheckActiveChannel. +func (s *EventHandlerService) HandleHomeChannelCreated(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.HomeChannelCreatedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Warn("channel not found in DB during HomeChannelCreated event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeHome { + logger.Warn("channel type mismatch during HomeChannelCreated event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) + return nil + } + if channel.Status >= core.ChannelStatusOpen { + logger.Warn("ignoring replayed HomeChannelCreated event on already-initialized channel", + "channelId", chanID, "currentStatus", channel.Status, "currentStateVersion", channel.StateVersion, "eventStateVersion", event.StateVersion) + return nil + } + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusOpen + + err = tx.UpdateChannel(*channel) + if err != nil { + return err + } + + if err := tx.RefreshUserEnforcedBalance(channel.UserWallet, channel.Asset); err != nil { + return err + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled HomeChannelCreated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// HandleHomeChannelMigrated processes the HomeChannelMigrated event emitted when a home channel +// is migrated to a new version or blockchain. This is currently not implemented and logs a warning. +// TODO: Implement HomeChannelMigrated handler logic +func (s *EventHandlerService) HandleHomeChannelMigrated(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.HomeChannelMigratedEvent) error { + logger := log.FromContext(ctx) + logger.Warn("unexpected HomeChannelMigrated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion) + return nil +} + +// HandleHomeChannelCheckpointed processes the HomeChannelCheckpointed event emitted when a channel +// state is successfully checkpointed on-chain. It updates the channel's state version and clears +// the Challenged status if present, returning the channel to Open — unless the local DB already +// holds a co-signed Finalize for this channel, in which case the post-Finalize Closing marker +// is restored instead. Without that restore, a Closing → Challenged → Open sequence driven by +// on-chain events would erase the fact that the node has already signed a finalized state, and +// CheckActiveChannel would let the user submit further transitions past the finalized state. +func (s *EventHandlerService) HandleHomeChannelCheckpointed(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.HomeChannelCheckpointedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during HomeChannelCheckpointed event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeHome { + logger.Warn("channel type mismatch during HomeChannelCheckpointed event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) + return nil + } + + // Acquire the user's balance-row lock before mutating channel status or + // backfilling the off-chain head. Receiver issuance paths lock the same row + // and then re-check Status; without this lock an RPC can read Status=Challenged, + // decide to store an unsigned receiver row, but commit after we flip to Open + // and backfill the prior head — leaving the latest head unsigned on an Open + // channel. See HandleHomeChannelClosed for the same pattern. + if _, err := tx.LockUserState(channel.UserWallet, channel.Asset); err != nil { + return err + } + + channel.StateVersion = event.StateVersion + + wasChallenged := channel.Status == core.ChannelStatusChallenged + if wasChallenged { + // Reconstruct the post-Finalize Closing marker from channel_states: if the node + // has already signed a Finalize state for this channel, the off-chain close is + // still pending and the channel must not return to Open. See the doc comment on + // HandleHomeChannelChallenged for the round-trip rationale. + finalized, err := tx.HasSignedFinalize(chanID) + if err != nil { + return err + } + if finalized { + channel.Status = core.ChannelStatusClosing + } else { + channel.Status = core.ChannelStatusOpen + } + // The challenge is resolved on chain; the expiry timestamp is no longer relevant + // and would otherwise surface as a stale deadline through the channel API. + channel.ChallengeExpiresAt = nil + } + + err = tx.UpdateChannel(*channel) + if err != nil { + return err + } + + if err := tx.RefreshUserEnforcedBalance(channel.UserWallet, channel.Asset); err != nil { + return err + } + + // Always backfill the user signature at the on-chain checkpointed version so the + // row matches what is enforced on chain. + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + // When a challenge is cleared, the off-chain head may sit above event.StateVersion: + // any receiver state issued during the challenge was stored unsigned and is now the + // channel's actual latest state. Backfill the node signature on that head so future + // flows treat it as fully co-signed. On normal Open→Open checkpoints the head row + // is already node-signed via the RPC path and this is a no-op. + if wasChallenged { + if err := s.backfillOffChainHeadNodeSig(ctx, tx, event.ChannelID); err != nil { + return err + } + } + + logger.Info("handled HomeChannelCheckpointed event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// backfillOffChainHeadNodeSig loads the off-chain head state for channelID (the highest +// stored version, regardless of signature status) and node-signs it when the row is +// present and the node signature is missing. The user signature is intentionally left +// untouched: when the head was created during a challenge it carries no user signature, +// and the user must countersign and acknowledge it via the regular RPC flow. +func (s *EventHandlerService) backfillOffChainHeadNodeSig(ctx context.Context, tx core.ChannelHubEventHandlerStore, channelID string) error { + head, err := tx.GetLastStateByChannelID(channelID, false) + if err != nil { + return err + } + if head == nil || head.NodeSig != nil { + return nil + } + // Per the challenge-clearance spec the only states accumulated during the dispute + // window are receiver credits (transfer_receive, release) — user-initiated ops are + // rejected upstream while the channel is Challenged. If the head is some other + // transition kind, the invariant has broken upstream and we must not silently + // node-sign it. Log it and bail so the caller surfaces the inconsistency. + if head.Transition.Type != core.TransitionTypeTransferReceive && + head.Transition.Type != core.TransitionTypeRelease { + log.FromContext(ctx).Warn("off-chain head after challenge clearance is not a receiver state, skipping node-sig backfill", + "channelId", channelID, + "transitionType", head.Transition.Type, + "version", head.Version, + ) + return nil + } + packed, err := s.statePacker.PackState(*head) + if err != nil { + return err + } + sig, err := s.nodeSigner.Sign(packed) + if err != nil { + return err + } + if err := tx.UpdateStateSigsIfMissing(channelID, head.Version, "", sig.String()); err != nil { + return err + } + log.FromContext(ctx).Info("backfilled missing node signature on off-chain head state", + "channelId", channelID, "stateVersion", head.Version) + return nil +} + +// HandleHomeChannelChallenged processes the HomeChannelChallenged event emitted when a potentially +// stale state is submitted on-chain. It marks the channel as Challenged and persists the challenge +// expiry so subsequent state-submission paths (CheckActiveChannel, RefreshUserEnforcedBalance) stop +// treating the channel as open. Automatic challenge response is intentionally disabled: the latest +// signed state may carry an intent (e.g. CLOSE, escrow initiate/finalize, migration) that cannot +// be resolved via ScheduleCheckpoint, and silently queueing an impossible transaction risks +// letting the challenge expire on a stale state. A warning is emitted so operators submit the +// appropriate on-chain action manually before expiry. +func (s *EventHandlerService) HandleHomeChannelChallenged(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.HomeChannelChallengedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during HomeChannelChallenged event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeHome { + logger.Warn("channel type mismatch during HomeChannelChallenged event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) + return nil + } + + // Acquire the user's balance-row lock before mutating channel status. Receiver + // issuance paths (issueTransferReceiverState / issueReleaseReceiverState) lock + // the same row up front and then re-check Status via CheckActiveChannel; without + // this lock an in-flight RPC can read Status=Open, node-sign a receiver state, + // and commit after we flip to Challenged — leaving a node-signed higher-version + // receiver state on a disputed channel. See HandleHomeChannelClosed for the same + // pattern. + if _, err := tx.LockUserState(channel.UserWallet, channel.Asset); err != nil { + return err + } + + if event.StateVersion < channel.StateVersion { + // Per protocol the challenged version cannot be lower than the last known on-chain version. + // Treat as an anomaly (replay, indexer mis-order, contract bug): warn and skip persistence. + logger.Warn("challenged state version is less than current channel state version, ignoring", "channelId", chanID, "currentStateVersion", channel.StateVersion, "challengedStateVersion", event.StateVersion) + return nil + } + + channel.StateVersion = event.StateVersion + // Closing → Challenged is an expected transition: a co-signed Finalize may race an + // on-chain challenge. The status field is intentionally overwritten — the chain takes + // precedence while the dispute is live. The post-Finalize fact is not lost: it is + // shadowed in channel_states (the latest fully-signed row carries TransitionTypeFinalize) + // and HandleHomeChannelCheckpointed restores ChannelStatusClosing from there when the + // challenge resolves, so the off-chain close flow resumes instead of silently regressing + // to Open. + channel.Status = core.ChannelStatusChallenged + expirationTime := time.Unix(int64(event.ChallengeExpiry), 0) + channel.ChallengeExpiresAt = &expirationTime + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + if err := tx.RefreshUserEnforcedBalance(channel.UserWallet, channel.Asset); err != nil { + return err + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Warn("home channel challenged", + "channelId", chanID, + "userWallet", channel.UserWallet, + "blockchainID", channel.BlockchainID, + "asset", channel.Asset, + "challengedStateVersion", event.StateVersion, + "challengeExpiry", expirationTime, + ) + return nil +} + +// HandleHomeChannelClosed processes the HomeChannelClosed event emitted when a home channel is +// finalized and closed on-chain. It updates the channel status to Closed and sets the final state version. +// Once closed, no further state updates are possible for this channel. +// +// Additionally, when the channel was locally Challenged at the time of close, the handler +// issues a ChallengeRescue state crediting the user the net receiver-minus-sender balance +// accrued strictly above the closure version. The rescue runs unconditionally on the +// Challenged → Closed transition: both path-1 (timeout on a stale candidate) and path-2 +// (cooperative close on a signed Finalize) routes pass through this branch, and the +// constructor + prev source pick the correct placement for the rescue row. +// +// MF3-I01 recovery anchor. This handler is the single recovery point for the wedge +// state described in audit finding MF3-I01: a receiver credit issued during the +// challenge window inherits HomeChannelID from currentState via NextState(), so the +// user's latest stored state can transiently point at a channel that closes via path-1 +// before the next receiver-credit issuance reads currentState again. The listener +// ordering & idempotency invariant (pkg/blockchain/evm/listener.go, see processEvents +// doc) guarantees HandleHomeChannelChallenged has already run for any path-1 close, so +// wasChallenged is true here and the rescue advances the user past the closed channel. +// Subsequent receiver-credit issuance reads the rescue row as currentState and no +// longer carries the closed channel reference, so request_creation can reopen on the +// same (wallet, asset) through the normal flow. +func (s *EventHandlerService) HandleHomeChannelClosed(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.HomeChannelClosedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during HomeChannelClosed event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeHome { + logger.Warn("channel type mismatch during HomeChannelClosed event", "channelId", chanID, "expectedType", core.ChannelTypeHome, "actualType", channel.Type) + return nil + } + + // Acquire the user's balance-row lock before mutating channel status or summing + // receiver credits. issueTransferReceiverState / issueReleaseReceiverState lock + // the same row up front, so this serializes the close against any in-flight RPC + // receiver-issuance for the same user: either the RPC commits its unsigned row + // before we sum (it lands in the rescue), or it blocks until we set the channel + // to Closed and then sees that via its own re-check. + if _, err := tx.LockUserState(channel.UserWallet, channel.Asset); err != nil { + return err + } + + wasChallenged := channel.Status == core.ChannelStatusChallenged + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusClosed + // Channel is terminal; any pending challenge deadline is no longer meaningful. + channel.ChallengeExpiresAt = nil + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + if err := tx.RefreshUserEnforcedBalance(channel.UserWallet, channel.Asset); err != nil { + return err + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + if wasChallenged { + if err := s.issueChallengeRescue(ctx, tx, channel, event.StateVersion); err != nil { + return err + } + } + + logger.Info("handled HomeChannelClosed event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// issueChallengeRescue emits a ChallengeRescue state on the user's ledger after a +// challenged-channel close. The state advances the user's chain past the closed +// channel so future receiver issuance and channels.v1.request_creation no longer +// wedge on it. +// +// Amount: NET effect on the user's home-channel balance of transitions stored +// strictly above closureVersion — receives (TransferReceive, Release) credit the +// user, sends (TransferSend, Commit) debit; everything else is excluded because it +// requires onchain backing the chain didn't enforce or belongs to a different +// ledger. Signed (Open-time) and unsigned (during-challenge) rows both contribute. +// Clamped at zero so an adversarial close at a version where the user's own balance +// was higher than the off-chain head can't dock the user further. +// +// Placement: prev is the user's latest state (across both channel-attached and +// detached rows). When prev is the in-channel head, the rescue wraps to a fresh +// epoch at (E+1, 0). When prev is a detached tip — the case where a node-signed +// Finalize already advanced the user via NextState() and post-Finalize receiver +// credits live at (E+1, v=0..M) with HomeChannelID nil — the rescue appends at +// (E+1, M+1), inheriting prev's ledger. NewChallengeRescueState picks the branch. +// +// AccountID on the rescue transition is the closed channel's ID; the rescue row +// itself has HomeChannelID nil — the shape of a credit to a user with no open home +// channel, to be folded into a signed state when the user next opens one. +func (s *EventHandlerService) issueChallengeRescue(ctx context.Context, tx core.ChannelHubEventHandlerStore, channel *core.Channel, closureVersion uint64) error { + logger := log.FromContext(ctx) + + // Strict `>` against closureVersion: the row at the closure version itself is the + // closing state and must be excluded — only transitions issued strictly after the + // dispute version are unenforced. A channel's in-channel rows live at a single + // epoch; detached post-Finalize rows have HomeChannelID NULL and are already + // excluded by the channel_id predicate, so no epoch filter is needed. + net, err := tx.SumNetTransitionAmountAfterVersion(channel.ChannelID, closureVersion) + if err != nil { + return err + } + + // Negative net is only reachable when the user closed at a version where her own + // channel balance was higher than the off-chain head (adversarial rollback of her + // own sends/commits). Onchain has already paid her above the head value; rescue + // must not dock further. Honest challenges typically have receives dominating, + // net >= 0. Clamp defensively and log. + total := net + if net.IsNegative() { + logger.Warn("challenge_rescue net is negative, clamping to zero", + "channelId", channel.ChannelID, + "netAmount", net.String(), + "closureVersion", closureVersion, + ) + total = decimal.Zero + } + + // prev is the user's latest state across both channel-attached and detached rows. + // When a node-signed Finalize already advanced the user via NextState() at sign + // time, prev is the detached tip and the rescue appends after it. Otherwise prev + // is the channel's own head and the rescue wraps to a fresh epoch. + prev, err := tx.GetLastUserState(channel.UserWallet, channel.Asset, false) + if err != nil { + return err + } + if prev == nil { + // Should not happen for a channel that reached Challenged → Closed: at least the + // closure state itself must be on file. Surface the inconsistency rather than + // silently dropping the rescue. + return fmt.Errorf("no state found for closed challenged channel %s", channel.ChannelID) + } + + rescue, err := core.NewChallengeRescueState(*prev, channel.ChannelID, total) + if err != nil { + return err + } + + // The rescue state is off-channel (HomeChannelID == nil) and is therefore not + // node-signed via the channel packer. It is treated like a credit to a user with no + // open home channel: the value is recorded in the user's state chain and will be + // folded into a properly signed state when the user next opens a channel. + if err := tx.StoreUserState(*rescue, ""); err != nil { + return err + } + + txn, err := core.NewTransactionFromTransition(nil, rescue, rescue.Transition) + if err != nil { + return err + } + if err := tx.RecordTransaction(*txn, ""); err != nil { + return err + } + + logger.Info("issued challenge_rescue state", + "channelId", channel.ChannelID, + "userWallet", channel.UserWallet, + "asset", channel.Asset, + "amount", total.String(), + "newStateID", rescue.ID, + "txID", txn.ID, + ) + return nil +} + +// HandleEscrowDepositInitiated processes the EscrowDepositInitiated event emitted when an escrow +// deposit operation begins on-chain. It updates the escrow channel status to Open, sets the state +// version, and schedules a checkpoint to finalize the deposit if a matching state exists in the database. +func (s *EventHandlerService) HandleEscrowDepositInitiated(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowDepositInitiatedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowDepositInitiated event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowDepositInitiated event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + return nil + } + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusOpen + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + state, err := tx.GetStateByChannelIDAndVersion(chanID, event.StateVersion) + if err != nil { + return err + } + if state == nil { + logger.Warn("no state found for channel during EscrowDepositInitiated event", "channelId", chanID) + } else { + if err := tx.ScheduleInitiateEscrowDeposit(state.ID, state.HomeLedger.BlockchainID); err != nil { + return err + } + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled EscrowDepositInitiated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// HandleEscrowDepositChallenged processes the EscrowDepositChallenged event emitted when an escrow +// deposit is challenged on-chain. It marks the channel as Challenged and sets the expiration time. +// Resolution policy depends on whether the node holds a newer fully-signed state for this channel: +// - If a newer signed FINALIZE_ESCROW_DEPOSIT exists, finalize the escrow on the non-home chain. +// - Otherwise the user is withholding finalize: defend the node allocation on the home chain by +// scheduling challengeChannel(...) with the INITIATE_ESCROW_DEPOSIT state. Without this, the user +// can let the non-home challenge expire, recover escrow-chain funds, and still threaten the +// home-chain finalize path against the node's locked allocation. +func (s *EventHandlerService) HandleEscrowDepositChallenged(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowDepositChallengedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowDepositChallenged event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowDepositChallenged event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + return nil + } + + if event.StateVersion < channel.StateVersion { + logger.Error("challenged escrow deposit state version is less than current channel state version", "channelId", chanID, "currentStateVersion", channel.StateVersion, "challengedStateVersion", event.StateVersion) + return nil + } + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusChallenged + + expirationTime := time.Unix(int64(event.ChallengeExpiry), 0) + channel.ChallengeExpiresAt = &expirationTime + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + lastSignedState, err := tx.GetLastStateByChannelID(chanID, true) + if err != nil { + return err + } + if lastSignedState != nil && lastSignedState.Version > event.StateVersion { + if lastSignedState.EscrowLedger == nil { + logger.Warn("last signed state has no escrow ledger during EscrowDepositChallenged event", "channelId", chanID) + } else { + if err := tx.ScheduleFinalizeEscrowDeposit(lastSignedState.ID, lastSignedState.EscrowLedger.BlockchainID); err != nil { + return err + } + } + } else { + if err := s.scheduleHomeChannelChallengeForEscrowDeposit(ctx, tx, chanID, event.StateVersion); err != nil { + return err + } + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled EscrowDepositChallenged event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// scheduleHomeChannelChallengeForEscrowDeposit queues a challengeChannel(...) submission on the home +// chain using the INITIATE_ESCROW_DEPOSIT state referenced by the escrow event. This anchors the +// home channel in DISPUTED so the user cannot later push a withheld FINALIZE state on home, and +// starts the home-chain challenge timer the operator uses to recover the node allocation. +func (s *EventHandlerService) scheduleHomeChannelChallengeForEscrowDeposit(ctx context.Context, tx core.ChannelHubEventHandlerStore, escrowChanID string, stateVersion uint64) error { + logger := log.FromContext(ctx) + + initiateState, err := tx.GetStateByChannelIDAndVersion(escrowChanID, stateVersion) + if err != nil { + return err + } + if initiateState == nil { + logger.Error("INITIATE_ESCROW_DEPOSIT state missing locally, cannot defend home channel automatically", "escrowChannelId", escrowChanID, "stateVersion", stateVersion) + return nil + } + if initiateState.HomeChannelID == nil { + logger.Error("INITIATE_ESCROW_DEPOSIT state has no home channel ID, cannot defend home channel automatically", "escrowChannelId", escrowChanID, "stateVersion", stateVersion) + return nil + } + + homeChannel, err := tx.GetChannelByID(*initiateState.HomeChannelID) + if err != nil { + return err + } + if homeChannel == nil { + logger.Error("home channel not found, cannot defend home channel automatically", "homeChannelId", *initiateState.HomeChannelID, "escrowChannelId", escrowChanID) + return nil + } + if homeChannel.Status != core.ChannelStatusOpen { + switch homeChannel.Status { + case core.ChannelStatusChallenged: + logger.Warn("home channel already Challenged, skipping auto-challenge", "homeChannelId", *initiateState.HomeChannelID, "escrowChannelId", escrowChanID) + case core.ChannelStatusClosed: + logger.Error("home channel Closed, defense window passed", "homeChannelId", *initiateState.HomeChannelID, "escrowChannelId", escrowChanID) + default: + logger.Warn("home channel not Open, skipping auto-challenge", "homeChannelId", *initiateState.HomeChannelID, "homeStatus", homeChannel.Status, "escrowChannelId", escrowChanID) + } + return nil + } + + if initiateState.HomeLedger.BlockchainID == 0 { + logger.Error("INITIATE_ESCROW_DEPOSIT state has zero home BlockchainID, cannot defend home channel automatically", "homeChannelId", *initiateState.HomeChannelID, "escrowChannelId", escrowChanID) + return nil + } + + if err := tx.ScheduleChallenge(initiateState.ID, initiateState.HomeLedger.BlockchainID); err != nil { + return err + } + + logger.Warn("scheduled home-channel challenge to defend node allocation against withheld escrow finalize", + "homeChannelId", *initiateState.HomeChannelID, + "escrowChannelId", escrowChanID, + "stateVersion", stateVersion, + ) + return nil +} + +// HandleEscrowDepositFinalized processes the EscrowDepositFinalized event emitted when an escrow +// deposit is successfully finalized on-chain. It updates the channel status to Closed and sets +// the final state version, completing the deposit lifecycle. +func (s *EventHandlerService) HandleEscrowDepositFinalized(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowDepositFinalizedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowDepositFinalized event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowDepositFinalized event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + return nil + } + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusClosed + // Channel is terminal; any pending challenge deadline is no longer meaningful. + channel.ChallengeExpiresAt = nil + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled EscrowDepositFinalized event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// HandleEscrowDepositsPurged processes the EscrowDepositsPurged event emitted when expired escrow deposits +// are finalized by the on-chain purge queue without a signed FINALIZE_ESCROW_DEPOSIT state. It marks each +// corresponding escrow channel as Closed, preserving its existing StateVersion. +// +// TODO: consider scoping the DB transaction per channel update instead of wrapping the whole batch, +// so a single failure does not roll back already-processed channels in the same purge event. +func (s *EventHandlerService) HandleEscrowDepositsPurged(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowDepositsPurgedEvent) error { + logger := log.FromContext(ctx) + closedCount := 0 + + for _, escrowID := range event.EscrowIDs { + channel, err := tx.GetChannelByID(escrowID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowDepositsPurged event", "escrowId", escrowID) + continue + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowDepositsPurged event", "escrowId", escrowID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + continue + } + if channel.Status == core.ChannelStatusClosed { + continue + } + + channel.Status = core.ChannelStatusClosed + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + closedCount++ + } + + logger.Info("handled EscrowDepositsPurged event", "purgedCount", len(event.EscrowIDs), "closedCount", closedCount) + return nil +} + +// HandleEscrowWithdrawalInitiated processes the EscrowWithdrawalInitiated event emitted when an escrow +// withdrawal operation begins on-chain. It updates the escrow channel status to Open and sets the state +// version to reflect the initiated withdrawal. +func (s *EventHandlerService) HandleEscrowWithdrawalInitiated(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowWithdrawalInitiatedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowWithdrawalInitiated event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowWithdrawalInitiated event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + return nil + } + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusOpen + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled EscrowWithdrawalInitiated event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// HandleEscrowWithdrawalChallenged processes the EscrowWithdrawalChallenged event emitted when an escrow +// withdrawal is challenged on-chain. It marks the channel as Challenged, sets the expiration time, +// and schedules a checkpoint for escrow withdrawal with the latest signed state to resolve the challenge. +func (s *EventHandlerService) HandleEscrowWithdrawalChallenged(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowWithdrawalChallengedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowWithdrawalChallenged event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowWithdrawalChallenged event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + return nil + } + + if event.StateVersion < channel.StateVersion { + logger.Error("challenged escrow withdrawal state version is less than current channel state version", "channelId", chanID, "currentStateVersion", channel.StateVersion, "challengedStateVersion", event.StateVersion) + return nil + } + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusChallenged + + expirationTime := time.Unix(int64(event.ChallengeExpiry), 0) + channel.ChallengeExpiresAt = &expirationTime + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + lastSignedState, err := tx.GetLastStateByChannelID(chanID, true) + if err != nil { + return err + } + if lastSignedState == nil { + logger.Warn("no state found for channel during EscrowWithdrawalChallenged event", "channelId", chanID) + } else if lastSignedState.Version <= event.StateVersion { + logger.Warn("last signed state version is not greater than challenged state version", "channelId", chanID, "lastSignedStateVersion", lastSignedState.Version, "challengedStateVersion", event.StateVersion) + } else { + if lastSignedState.EscrowLedger == nil { + logger.Warn("last signed state has no escrow ledger during EscrowWithdrawalChallenged event", "channelId", chanID) + } else { + if err := tx.ScheduleFinalizeEscrowWithdrawal(lastSignedState.ID, lastSignedState.EscrowLedger.BlockchainID); err != nil { + return err + } + } + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled EscrowWithdrawalChallenged event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +// HandleEscrowWithdrawalFinalized processes the EscrowWithdrawalFinalized event emitted when an escrow +// withdrawal is successfully finalized on-chain. It updates the channel status to Closed and sets +// the final state version, completing the withdrawal lifecycle. +func (s *EventHandlerService) HandleEscrowWithdrawalFinalized(ctx context.Context, tx core.ChannelHubEventHandlerStore, event *core.EscrowWithdrawalFinalizedEvent) error { + logger := log.FromContext(ctx) + chanID := event.ChannelID + channel, err := tx.GetChannelByID(chanID) + if err != nil { + return err + } + if channel == nil { + logger.Debug("channel not found in DB during EscrowWithdrawalFinalized event", "channelId", chanID) + return nil + } + if channel.Type != core.ChannelTypeEscrow { + logger.Warn("channel type mismatch during EscrowWithdrawalFinalized event", "channelId", chanID, "expectedType", core.ChannelTypeEscrow, "actualType", channel.Type) + return nil + } + + channel.StateVersion = event.StateVersion + channel.Status = core.ChannelStatusClosed + // Channel is terminal; any pending challenge deadline is no longer meaningful. + channel.ChallengeExpiresAt = nil + + if err := tx.UpdateChannel(*channel); err != nil { + return err + } + + if err := tx.UpdateStateSigsIfMissing(event.ChannelID, event.StateVersion, event.UserSig, ""); err != nil { + return err + } + + logger.Info("handled EscrowWithdrawalFinalized event", "channelId", event.ChannelID, "stateVersion", event.StateVersion, "userWallet", channel.UserWallet) + return nil +} + +func (s *EventHandlerService) HandleUserLockedBalanceUpdated(ctx context.Context, tx core.LockingContractEventHandlerStore, event *core.UserLockedBalanceUpdatedEvent) error { + logger := log.FromContext(ctx) + 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/nitronode/event_handlers/service_test.go b/nitronode/event_handlers/service_test.go new file mode 100644 index 000000000..def94ca2a --- /dev/null +++ b/nitronode/event_handlers/service_test.go @@ -0,0 +1,2154 @@ +package event_handlers + +import ( + "context" + "errors" + "testing" + "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/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/sign" +) + +func TestHandleHomeChannelCreated_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusVoid, + StateVersion: 0, + } + + event := &core.HomeChannelCreatedEvent{ + ChannelID: channelID, + StateVersion: 1, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusOpen && + ch.StateVersion == 1 + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(1), "", "").Return(nil) + + // Execute + err := service.HandleHomeChannelCreated(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleHomeChannelCreated_IgnoresReplayOnInitializedChannel(t *testing.T) { + // HomeChannelCreated must fire only once per channel (Void → Open). Any later replay — + // indexer restart, chain reorg, block reprocessing — would otherwise clobber the current + // status, including resetting a Closing channel back to Open and re-arming the submission + // gate past a co-signed Finalize. The handler must short-circuit when the channel is no + // longer in Void. + cases := []struct { + name string + status core.ChannelStatus + }{ + {"Open", core.ChannelStatusOpen}, + {"Challenged", core.ChannelStatusChallenged}, + {"Closing", core.ChannelStatusClosing}, + {"Closed", core.ChannelStatusClosed}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: "0x1234567890123456789012345678901234567890", + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: tc.status, + StateVersion: 5, + } + + event := &core.HomeChannelCreatedEvent{ + ChannelID: channelID, + StateVersion: 1, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + + err := service.HandleHomeChannelCreated(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "UpdateChannel", mock.Anything) + mockStore.AssertNotCalled(t, "RefreshUserEnforcedBalance", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "UpdateStateSigsIfMissing", mock.Anything, mock.Anything, mock.Anything, mock.Anything) + }) + } +} + +func TestHandleHomeChannelCheckpointed_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + // Test data + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 3, + ChallengeExpiresAt: &expiryTime, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: 5, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusOpen && + ch.StateVersion == 5 && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(5), "", "").Return(nil) + // No co-signed Finalize → status restored to Open (not Closing). + mockStore.On("HasSignedFinalize", channelID).Return(false, nil) + // Off-chain head missing → no head-sig backfill. + mockStore.On("GetLastStateByChannelID", channelID, false).Return(nil, nil) + + // Execute + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleHomeChannelChallenged_PersistsChallenge(t *testing.T) { + // Channel must be marked Challenged with the challenge expiry so CheckActiveChannel and + // RefreshUserEnforcedBalance stop treating it as open. Auto-checkpoint stays disabled: + // non-checkpointable intents (CLOSE, escrow initiate/finalize, migration) cannot be + // resolved via ScheduleCheckpoint, so operator action is required. + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 3, + } + + event := &core.HomeChannelChallengedEvent{ + ChannelID: channelID, + StateVersion: 4, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusChallenged && + ch.StateVersion == 4 && + ch.ChallengeExpiresAt != nil && + ch.ChallengeExpiresAt.Unix() == int64(challengeExpiry) + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(4), "", "").Return(nil) + + err := service.HandleHomeChannelChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "GetLastStateByChannelID", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "ScheduleCheckpoint", mock.Anything, mock.Anything) +} + +func TestHandleHomeChannelChallenged_StaleVersionIgnored(t *testing.T) { + // Per protocol the challenged version cannot be lower than the last known on-chain version. + // Anomalies (replay, indexer mis-order) must not regress channel state. + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 5, + } + + event := &core.HomeChannelChallengedEvent{ + ChannelID: channelID, + StateVersion: 3, + ChallengeExpiry: uint64(time.Now().Add(time.Hour).Unix()), + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + + err := service.HandleHomeChannelChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "UpdateChannel", mock.Anything) + mockStore.AssertNotCalled(t, "RefreshUserEnforcedBalance", mock.Anything, mock.Anything) +} + +func TestHandleHomeChannelChallenged_ChannelNotFound(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xMissingChannel" + + event := &core.HomeChannelChallengedEvent{ + ChannelID: channelID, + StateVersion: 1, + ChallengeExpiry: uint64(time.Now().Add(time.Hour).Unix()), + } + + mockStore.On("GetChannelByID", channelID).Return(nil, nil) + + err := service.HandleHomeChannelChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleHomeChannelChallenged_TypeMismatch(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xEscrowAsHome" + + channel := &core.Channel{ + ChannelID: channelID, + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + } + + event := &core.HomeChannelChallengedEvent{ + ChannelID: channelID, + StateVersion: 1, + ChallengeExpiry: uint64(time.Now().Add(time.Hour).Unix()), + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + + err := service.HandleHomeChannelChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleHomeChannelChallenged_FromClosingState(t *testing.T) { + // Closing → Challenged is an expected transition when a co-signed Finalize races an + // on-chain challenge: the chain takes precedence while the dispute is live and the + // status field is intentionally overwritten. The post-Finalize fact survives in + // channel_states; HandleHomeChannelCheckpointed restores Closing from there. See + // TestHandleHomeChannelCheckpointed_FromChallengedWithSignedFinalize for the + // completing half of the round-trip. + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusClosing, + StateVersion: 3, + } + + event := &core.HomeChannelChallengedEvent{ + ChannelID: channelID, + StateVersion: 4, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusChallenged && + ch.StateVersion == 4 && + ch.ChallengeExpiresAt != nil && + ch.ChallengeExpiresAt.Unix() == int64(challengeExpiry) + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(4), "", "").Return(nil) + + err := service.HandleHomeChannelChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelChallenged_AcquiresUserLockBeforeMutation pins the race fix: +// the handler must call LockUserState(userWallet, asset) before UpdateChannel so an +// in-flight receiver-issuance RPC cannot read Status=Open, node-sign a receiver state +// and commit after the status flip to Challenged. See HandleHomeChannelClosed for the +// same pattern. +func TestHandleHomeChannelChallenged_AcquiresUserLockBeforeMutation(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "usdc" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 3, + } + + event := &core.HomeChannelChallengedEvent{ + ChannelID: channelID, + StateVersion: 4, + ChallengeExpiry: uint64(time.Now().Add(time.Hour).Unix()), + } + + var locked bool + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset). + Run(func(mock.Arguments) { locked = true }). + Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(core.Channel) bool { + require.True(t, locked, "LockUserState must be called before UpdateChannel") + return true + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(4), "", "").Return(nil) + + err := service.HandleHomeChannelChallenged(ctx, mockStore, event) + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleHomeChannelClosed_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 5, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: 10, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusClosed && + ch.StateVersion == 10 + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(10), "", "").Return(nil) + + // Execute + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowDepositInitiated_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xEscrowChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusVoid, + StateVersion: 0, + } + + state := &core.State{ + ID: "state123", + Version: 1, + HomeLedger: core.Ledger{ + BlockchainID: 0, + }, + EscrowLedger: &core.Ledger{ + BlockchainID: 2, + }, + } + + event := &core.EscrowDepositInitiatedEvent{ + ChannelID: channelID, + StateVersion: 1, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusOpen && + ch.StateVersion == 1 + })).Return(nil) + mockStore.On("GetStateByChannelIDAndVersion", channelID, uint64(1)).Return(state, nil) + mockStore.On("ScheduleInitiateEscrowDeposit", "state123", uint64(0)).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(1), "", "").Return(nil) + + // Execute + err := service.HandleEscrowDepositInitiated(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowDepositChallenged_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xEscrowChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + state := &core.State{ + ID: "state123", + Version: 5, + EscrowLedger: &core.Ledger{ + BlockchainID: 2, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: channelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusChallenged && + ch.StateVersion == 3 && + ch.ChallengeExpiresAt != nil + })).Return(nil) + mockStore.On("GetLastStateByChannelID", channelID, true).Return(state, nil) + mockStore.On("ScheduleFinalizeEscrowDeposit", "state123", uint64(2)).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(3), "", "").Return(nil) + + // Execute + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowDepositChallenged_NoFinalize_SchedulesHomeChallenge(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + homeChannelID := "0xHomeChannel456" + userWallet := "0x1234567890123456789012345678901234567890" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + homeChannel := &core.Channel{ + ChannelID: homeChannelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + BlockchainID: 1, + } + + initiateState := &core.State{ + ID: "initiate-state-id", + Version: 3, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + BlockchainID: 1, + }, + EscrowLedger: &core.Ledger{ + BlockchainID: 2, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == escrowChannelID && + ch.Status == core.ChannelStatusChallenged && + ch.StateVersion == 3 + })).Return(nil) + // No newer signed FINALIZE state available locally — node only has the INITIATE state. + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(initiateState, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(initiateState, nil) + mockStore.On("GetChannelByID", homeChannelID).Return(homeChannel, nil) + mockStore.On("ScheduleChallenge", "initiate-state-id", uint64(1)).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", escrowChannelID, uint64(3), "", "").Return(nil) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleFinalizeEscrowDeposit", mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_NoFinalize_HomeChannelNotOpen_SkipsChallenge(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + homeChannelID := "0xHomeChannel456" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + homeChannel := &core.Channel{ + ChannelID: homeChannelID, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + BlockchainID: 1, + } + + initiateState := &core.State{ + ID: "initiate-state-id", + Version: 3, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + BlockchainID: 1, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(initiateState, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(initiateState, nil) + mockStore.On("GetChannelByID", homeChannelID).Return(homeChannel, nil) + mockStore.On("UpdateStateSigsIfMissing", escrowChannelID, uint64(3), "", "").Return(nil) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "ScheduleFinalizeEscrowDeposit", mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_NoLocalState_NoSchedule(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(nil, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(nil, nil) + mockStore.On("UpdateStateSigsIfMissing", escrowChannelID, uint64(3), "", "").Return(nil) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "ScheduleFinalizeEscrowDeposit", mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_HomeChannelIDNil_SkipsChallenge(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + initiateState := &core.State{ + ID: "initiate-state-id", + Version: 3, + HomeChannelID: nil, + HomeLedger: core.Ledger{ + BlockchainID: 1, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(initiateState, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(initiateState, nil) + mockStore.On("UpdateStateSigsIfMissing", escrowChannelID, uint64(3), "", "").Return(nil) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "ScheduleFinalizeEscrowDeposit", mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_HomeChannelNotFound_SkipsChallenge(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + homeChannelID := "0xHomeChannel456" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + initiateState := &core.State{ + ID: "initiate-state-id", + Version: 3, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + BlockchainID: 1, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(initiateState, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(initiateState, nil) + mockStore.On("GetChannelByID", homeChannelID).Return((*core.Channel)(nil), nil) + mockStore.On("UpdateStateSigsIfMissing", escrowChannelID, uint64(3), "", "").Return(nil) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "ScheduleFinalizeEscrowDeposit", mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_GetStateByVersionError_Propagates(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + dbErr := errors.New("db boom") + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(nil, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(nil, dbErr) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.ErrorIs(t, err, dbErr) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "UpdateStateSigsIfMissing", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_GetHomeChannelError_Propagates(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + homeChannelID := "0xHomeChannel456" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + initiateState := &core.State{ + ID: "initiate-state-id", + Version: 3, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + BlockchainID: 1, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + dbErr := errors.New("db boom") + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(initiateState, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(initiateState, nil) + mockStore.On("GetChannelByID", homeChannelID).Return((*core.Channel)(nil), dbErr) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.ErrorIs(t, err, dbErr) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "UpdateStateSigsIfMissing", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositChallenged_HomeBlockchainIDZero_SkipsChallenge(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + escrowChannelID := "0xEscrowChannel123" + homeChannelID := "0xHomeChannel456" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + escrowChannel := &core.Channel{ + ChannelID: escrowChannelID, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + homeChannel := &core.Channel{ + ChannelID: homeChannelID, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + BlockchainID: 1, + } + + initiateState := &core.State{ + ID: "initiate-state-id", + Version: 3, + HomeChannelID: &homeChannelID, + HomeLedger: core.Ledger{ + BlockchainID: 0, + }, + } + + event := &core.EscrowDepositChallengedEvent{ + ChannelID: escrowChannelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + mockStore.On("GetChannelByID", escrowChannelID).Return(escrowChannel, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("GetLastStateByChannelID", escrowChannelID, true).Return(initiateState, nil) + mockStore.On("GetStateByChannelIDAndVersion", escrowChannelID, uint64(3)).Return(initiateState, nil) + mockStore.On("GetChannelByID", homeChannelID).Return(homeChannel, nil) + mockStore.On("UpdateStateSigsIfMissing", escrowChannelID, uint64(3), "", "").Return(nil) + + err := service.HandleEscrowDepositChallenged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "ScheduleChallenge", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "ScheduleFinalizeEscrowDeposit", mock.Anything, mock.Anything) +} + +func TestHandleEscrowDepositFinalized_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xEscrowChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusChallenged, + StateVersion: 3, + ChallengeExpiresAt: &expiryTime, + } + + event := &core.EscrowDepositFinalizedEvent{ + ChannelID: channelID, + StateVersion: 5, + } + + // Mock expectations — Finalized resolves any pending challenge and clears its expiry. + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusClosed && + ch.StateVersion == 5 && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(5), "", "").Return(nil) + + // Execute + err := service.HandleEscrowDepositFinalized(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowWithdrawalInitiated_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xEscrowChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusVoid, + StateVersion: 0, + } + + event := &core.EscrowWithdrawalInitiatedEvent{ + ChannelID: channelID, + StateVersion: 1, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusOpen && + ch.StateVersion == 1 + })).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(1), "", "").Return(nil) + + // Execute + err := service.HandleEscrowWithdrawalInitiated(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowWithdrawalChallenged_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xEscrowChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + challengeExpiry := uint64(time.Now().Add(time.Hour).Unix()) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + state := &core.State{ + ID: "state123", + Version: 5, + EscrowLedger: &core.Ledger{ + BlockchainID: 2, + }, + } + + event := &core.EscrowWithdrawalChallengedEvent{ + ChannelID: channelID, + StateVersion: 3, + ChallengeExpiry: challengeExpiry, + } + + // Mock expectations + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusChallenged && + ch.StateVersion == 3 && + ch.ChallengeExpiresAt != nil + })).Return(nil) + mockStore.On("GetLastStateByChannelID", channelID, true).Return(state, nil) + mockStore.On("ScheduleFinalizeEscrowWithdrawal", "state123", uint64(2)).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(3), "", "").Return(nil) + + // Execute + err := service.HandleEscrowWithdrawalChallenged(ctx, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowWithdrawalFinalized_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // Test data + channelID := "0xEscrowChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusChallenged, + StateVersion: 3, + ChallengeExpiresAt: &expiryTime, + } + + event := &core.EscrowWithdrawalFinalizedEvent{ + ChannelID: channelID, + StateVersion: 5, + } + + // Mock expectations — Finalized resolves any pending challenge and clears its expiry. + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusClosed && + ch.StateVersion == 5 && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(5), "", "").Return(nil) + + // Execute + err := service.HandleEscrowWithdrawalFinalized(ctx, mockStore, event) + + // Assert + 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{} + + // 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, mockStore, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelCheckpointed_BackfillsUserSig covers the recovery path for the wedge +// scenario: a node-only state was checkpointed on chain (e.g. the receiver of a transfer signed +// the receiver state and submitted it directly). The reactor extracts the user signature from the +// event and the handler must forward it to the store so the local row matches what is enforced +// on chain. Without this, EnsureNoOngoingStateTransitions stays blocked on the now-stale prior +// bilateral state and the channel can only be unblocked via on-chain challenge. +func TestHandleHomeChannelCheckpointed_BackfillsUserSig(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + userSig := "0xabcdef0123456789" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 4, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: 5, + UserSig: userSig, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.StateVersion == 5 + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(5), userSig, "").Return(nil) + + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "GetLastStateByChannelID", mock.Anything, mock.Anything) +} + +// TestHandleHomeChannelCheckpointed_BackfillError surfaces store errors from the backfill so +// the surrounding event-processing transaction rolls back and the event can be retried. +func TestHandleHomeChannelCheckpointed_BackfillError(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 4, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: 5, + UserSig: "0xdeadbeef", + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "usdc").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "usdc").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(5), "0xdeadbeef", "").Return(errors.New("db error")) + + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + + require.Error(t, err) + require.Contains(t, err.Error(), "db error") + mockStore.AssertExpectations(t) +} + +// mockEventHandlerAssetStore implements core.AssetStore for state packing in unit tests. +type mockEventHandlerAssetStore struct{} + +func (mockEventHandlerAssetStore) GetAssetDecimals(string) (uint8, error) { return 6, nil } +func (mockEventHandlerAssetStore) GetTokenDecimals(uint64, string) (uint8, error) { + return 6, nil +} + +func newTestEventHandlerService(t *testing.T) (*EventHandlerService, string) { + t.Helper() + key, err := crypto.GenerateKey() + require.NoError(t, err) + signer, err := sign.NewEthereumMsgSigner(hexutil.Encode(crypto.FromECDSA(key))) + require.NoError(t, err) + nodeSigner, err := core.NewChannelDefaultSigner(signer) + require.NoError(t, err) + packer := core.NewStatePackerV1(mockEventHandlerAssetStore{}) + return NewEventHandlerService(nodeSigner, packer), signer.PublicKey().Address().String() +} + +// TestHandleHomeChannelCheckpointed_BackfillsHeadNodeSig covers the case where a +// challenge is cleared while the off-chain head sits above the checkpointed onchain +// version: a receiver state stored unsigned during the challenge window is now the +// channel's actual latest state. The handler must node-sign that head so future flows +// treat it as fully co-signed; the user signature backfill targets the on-chain version +// separately. +func TestHandleHomeChannelCheckpointed_BackfillsHeadNodeSig(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, nodeAddress := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + homeChannelIDPtr := channelID + checkpointVersion := uint64(5) + headVersion := uint64(7) + checkpointUserSig := "0xusersighex" + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 4, + ChallengeExpiresAt: &expiryTime, + } + + // Off-chain head is a during-challenge receiver state above the on-chain checkpoint. + headState := &core.State{ + ID: core.GetStateID(userWallet, asset, 1, headVersion), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: headVersion, + HomeChannelID: &homeChannelIDPtr, + Transition: core.Transition{Type: core.TransitionTypeTransferReceive}, + HomeLedger: core.Ledger{ + TokenAddress: "0xtoken", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(100), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: checkpointVersion, + UserSig: checkpointUserSig, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.Status == core.ChannelStatusOpen && + ch.StateVersion == checkpointVersion && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + // User-sig backfill at the on-chain version. + mockStore.On("UpdateStateSigsIfMissing", channelID, checkpointVersion, checkpointUserSig, "").Return(nil) + // No co-signed Finalize → Challenged restores to Open. + mockStore.On("HasSignedFinalize", channelID).Return(false, nil) + // Off-chain head lookup returns the higher unsigned receiver state. + mockStore.On("GetLastStateByChannelID", channelID, false).Return(headState, nil) + + var capturedNodeSig string + mockStore.On("UpdateStateSigsIfMissing", channelID, headVersion, "", mock.AnythingOfType("string")). + Run(func(args mock.Arguments) { + capturedNodeSig = args.String(3) + }).Return(nil) + + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + require.NoError(t, err) + require.NotEmpty(t, capturedNodeSig, "node signature must be populated on backfill") + + // Verify the produced signature is from the configured node key. + packer := core.NewStatePackerV1(mockEventHandlerAssetStore{}) + packed, err := packer.PackState(*headState) + require.NoError(t, err) + sigBytes, err := hexutil.Decode(capturedNodeSig) + require.NoError(t, err) + validator := core.NewChannelSigValidator(nil) + require.NoError(t, validator.Verify(nodeAddress, packed, sigBytes)) + + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelCheckpointed_HeadAlreadySigned_NoBackfill verifies that when +// a challenge clears and the off-chain head is already node-signed (typical case if +// no receiver states were issued during the challenge), the handler does not re-sign. +func TestHandleHomeChannelCheckpointed_HeadAlreadySigned_NoBackfill(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + homeChannelIDPtr := channelID + checkpointVersion := uint64(5) + headVersion := uint64(5) + existingNodeSig := "0xnodesigalreadyhere" + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 4, + ChallengeExpiresAt: &expiryTime, + } + + headState := &core.State{ + ID: core.GetStateID(userWallet, asset, 1, headVersion), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: headVersion, + HomeChannelID: &homeChannelIDPtr, + Transition: core.Transition{Type: core.TransitionTypeTransferReceive}, + HomeLedger: core.Ledger{ + TokenAddress: "0xtoken", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(100), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + NodeSig: &existingNodeSig, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: checkpointVersion, + UserSig: "0xusersig", + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.Status == core.ChannelStatusOpen && + ch.StateVersion == checkpointVersion && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, checkpointVersion, "0xusersig", "").Return(nil) + // No co-signed Finalize → Challenged restores to Open. + mockStore.On("HasSignedFinalize", channelID).Return(false, nil) + mockStore.On("GetLastStateByChannelID", channelID, false).Return(headState, nil) + + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + require.NoError(t, err) + + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "UpdateStateSigsIfMissing", channelID, headVersion, "", mock.AnythingOfType("string")) +} + +// TestHandleHomeChannelCheckpointed_FromChallengedWithSignedFinalize exercises the post-Finalize +// regression scenario: a Closing channel (node signed Finalize off-chain) was flipped to +// Challenged by a stale on-chain challenge for a lower version. When a subsequent Checkpointed +// event clears the dispute, the handler must NOT drop the status back to Open, because the local +// DB still holds a co-signed Finalize. Restoring Closing keeps CheckActiveChannel excluding the +// channel and prevents the user from advancing past the finalized state via SubmitState. +func TestHandleHomeChannelCheckpointed_FromChallengedWithSignedFinalize(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "usdc" + homeChannelIDPtr := channelID + finalizeVersion := uint64(7) + checkpointVersion := uint64(6) + userSig := "0xusersig" + nodeSig := "0xnodesig" + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 5, + ChallengeExpiresAt: &expiryTime, + } + + // Co-signed Finalize sitting above the checkpointed on-chain version. + finalizeState := &core.State{ + ID: core.GetStateID(userWallet, asset, 1, finalizeVersion), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: finalizeVersion, + HomeChannelID: &homeChannelIDPtr, + Transition: core.Transition{Type: core.TransitionTypeFinalize}, + HomeLedger: core.Ledger{ + TokenAddress: "0xtoken", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(100), + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: checkpointVersion, + UserSig: userSig, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == channelID && + ch.Status == core.ChannelStatusClosing && + ch.StateVersion == checkpointVersion && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, checkpointVersion, userSig, "").Return(nil) + // Node-signed Finalize exists → status restored to Closing. + mockStore.On("HasSignedFinalize", channelID).Return(true, nil) + // Backfill path: off-chain head is the same already-signed Finalize state — no-op. + mockStore.On("GetLastStateByChannelID", channelID, false).Return(finalizeState, nil) + + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) + // Head already node-signed → no second backfill call. + mockStore.AssertNotCalled(t, "UpdateStateSigsIfMissing", channelID, finalizeVersion, mock.Anything, mock.Anything) +} + +// TestHandleHomeChannelCheckpointed_AcquiresUserLockBeforeMutation pins the race fix: +// the handler must call LockUserState before flipping Status from Challenged to Open +// and backfilling the off-chain head. Otherwise an in-flight receiver-issuance RPC +// can read Status=Challenged, choose to store an unsigned receiver row, and commit +// after the backfill — leaving the latest head unsigned on a now-Open channel. +func TestHandleHomeChannelCheckpointed_AcquiresUserLockBeforeMutation(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "usdc" + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 3, + } + + event := &core.HomeChannelCheckpointedEvent{ + ChannelID: channelID, + StateVersion: 5, + } + + var locked bool + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset). + Run(func(mock.Arguments) { locked = true }). + Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(core.Channel) bool { + require.True(t, locked, "LockUserState must be called before UpdateChannel") + return true + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, uint64(5), "", "").Return(nil) + // No co-signed Finalize → status restored to Open. + mockStore.On("HasSignedFinalize", channelID).Return(false, nil) + mockStore.On("GetLastStateByChannelID", channelID, false).Return(nil, nil) + + err := service.HandleHomeChannelCheckpointed(ctx, mockStore, event) + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelClosed_ChallengeRescue_Squash exercises the path where a channel +// is closed onchain while still Challenged: any unsigned receiver-state credits accrued +// during the challenge window are squashed into a single ChallengeRescue state on the +// user's ledger. +func TestHandleHomeChannelClosed_ChallengeRescue_Squash(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + tokenAddress := "0xtoken" + blockchainID := uint64(1) + closureVersion := uint64(7) + rescueAmount := decimal.NewFromInt(150) + homeChannelIDPtr := channelID + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 5, + } + + // Latest stored state for the channel — represents the highest-version unsigned + // receiver state recorded during the challenge window. + prevState := &core.State{ + ID: core.GetStateID(userWallet, asset, 1, 9), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 9, + HomeChannelID: &homeChannelIDPtr, + HomeLedger: core.Ledger{ + TokenAddress: tokenAddress, + BlockchainID: blockchainID, + UserBalance: decimal.NewFromInt(50), + UserNetFlow: decimal.NewFromInt(50), + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: closureVersion, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.Status == core.ChannelStatusClosed && ch.StateVersion == closureVersion + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, closureVersion, "", "").Return(nil) + mockStore.On("SumNetTransitionAmountAfterVersion", channelID, closureVersion).Return(rescueAmount, nil) + mockStore.On("GetLastUserState", userWallet, asset, false).Return(prevState, nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), "").Return(nil) + var capturedTx core.Transaction + mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + capturedTx = tx + return true + }), "").Return(nil) + + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + require.NoError(t, err) + + require.Equal(t, core.TransitionTypeChallengeRescue, capturedState.Transition.Type) + require.Equal(t, channelID, capturedState.Transition.AccountID) + require.True(t, rescueAmount.Equal(capturedState.Transition.Amount)) + require.Nil(t, capturedState.HomeChannelID, "rescue state must be off-channel") + require.Equal(t, prevState.Epoch+1, capturedState.Epoch) + require.Equal(t, uint64(0), capturedState.Version) + require.Nil(t, capturedState.NodeSig, "rescue state is stored unsigned, like a credit to a user with no open home channel") + require.True(t, rescueAmount.Equal(capturedState.HomeLedger.UserBalance)) + require.Empty(t, capturedState.HomeLedger.TokenAddress) + require.Equal(t, uint64(0), capturedState.HomeLedger.BlockchainID) + + require.Equal(t, core.TransactionTypeChallengeRescue, capturedTx.TxType) + require.Equal(t, channelID, capturedTx.FromAccount) + require.Equal(t, userWallet, capturedTx.ToAccount) + require.True(t, rescueAmount.Equal(capturedTx.Amount)) + + // TxID is derived deterministically from (fromAccount, newStateID). + expectedTxID, err := core.GetReceiverTransactionID(channelID, capturedState.ID) + require.NoError(t, err) + require.Equal(t, expectedTxID, capturedTx.ID) + + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelClosed_ChallengeRescue_NoCredits covers the path where a channel +// is closed while Challenged but no unsigned receiver credits accrued. A zero-amount +// rescue state is still emitted so the user's latest stored state moves to a fresh +// epoch with HomeChannelID nil; without it, future receiver-state issuance and +// channels.v1.request_creation would stay wedged on the closed channel. +func TestHandleHomeChannelClosed_ChallengeRescue_NoCredits(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + tokenAddress := "0xtoken" + blockchainID := uint64(1) + closureVersion := uint64(7) + homeChannelIDPtr := channelID + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 5, + ChallengeExpiresAt: &expiryTime, + } + + prevState := &core.State{ + ID: core.GetStateID(userWallet, asset, 1, closureVersion), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: closureVersion, + HomeChannelID: &homeChannelIDPtr, + HomeLedger: core.Ledger{ + TokenAddress: tokenAddress, + BlockchainID: blockchainID, + UserBalance: decimal.NewFromInt(50), + UserNetFlow: decimal.NewFromInt(50), + }, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: closureVersion, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + // Terminal Close clears any lingering challenge expiry alongside the status flip. + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.Status == core.ChannelStatusClosed && + ch.StateVersion == closureVersion && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, closureVersion, "", "").Return(nil) + mockStore.On("SumNetTransitionAmountAfterVersion", channelID, closureVersion).Return(decimal.Zero, nil) + mockStore.On("GetLastUserState", userWallet, asset, false).Return(prevState, nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), "").Return(nil) + mockStore.On("RecordTransaction", mock.Anything, "").Return(nil) + + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + require.NoError(t, err) + + require.Equal(t, core.TransitionTypeChallengeRescue, capturedState.Transition.Type) + require.True(t, capturedState.Transition.Amount.IsZero()) + require.True(t, capturedState.HomeLedger.UserBalance.IsZero()) + require.Nil(t, capturedState.HomeChannelID) + require.Equal(t, prevState.Epoch+1, capturedState.Epoch) + require.Equal(t, uint64(0), capturedState.Version) + + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelClosed_ChallengeRescue_NegativeNet_ClampsToZero pins the +// adversarial-rollback case: the user closes at a version where her own channel +// balance was higher than the off-chain head, so the net transition amount above +// closure is negative. The rescue must clamp the credit at zero — onchain has +// already paid above the head value, and docking the user further is not the +// rescue's job. A zero-amount rescue is still issued so the user state head +// advances to a fresh epoch with HomeChannelID nil. +func TestHandleHomeChannelClosed_ChallengeRescue_NegativeNet_ClampsToZero(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + tokenAddress := "0xtoken" + blockchainID := uint64(1) + closureVersion := uint64(2) + homeChannelIDPtr := channelID + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 1, + } + + prevState := &core.State{ + ID: core.GetStateID(userWallet, asset, 1, 5), + Asset: asset, + UserWallet: userWallet, + Epoch: 1, + Version: 5, + HomeChannelID: &homeChannelIDPtr, + HomeLedger: core.Ledger{ + TokenAddress: tokenAddress, + BlockchainID: blockchainID, + UserBalance: decimal.NewFromInt(51), + UserNetFlow: decimal.NewFromInt(51), + }, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: closureVersion, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, closureVersion, "", "").Return(nil) + mockStore.On("SumNetTransitionAmountAfterVersion", channelID, closureVersion).Return(decimal.NewFromInt(-49), nil) + mockStore.On("GetLastUserState", userWallet, asset, false).Return(prevState, nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), "").Return(nil) + mockStore.On("RecordTransaction", mock.Anything, "").Return(nil) + + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + require.NoError(t, err) + + require.Equal(t, core.TransitionTypeChallengeRescue, capturedState.Transition.Type) + require.True(t, capturedState.Transition.Amount.IsZero(), "negative net must clamp to zero, got %s", capturedState.Transition.Amount.String()) + require.True(t, capturedState.HomeLedger.UserBalance.IsZero()) + require.Nil(t, capturedState.HomeChannelID) + require.Equal(t, prevState.Epoch+1, capturedState.Epoch) + require.Equal(t, uint64(0), capturedState.Version) + + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelClosed_TimeoutAfterFinalize_AppendsRescue pins the path-1 +// timeout close arriving after a node-signed Finalize already advanced the user's +// chain. The chain settled at a version Y strictly below the Finalize version F: +// the user's true off-chain claim was userAlloc(F), but on-chain only userAlloc(Y) +// was paid. The shortfall is the net of receiver/sender transitions in (Y, F]. +// +// At sign time, NextState() created a detached fresh-epoch chain at (E+1, v=0..M) +// holding post-Finalize receiver credits with HomeChannelID nil. Placing the rescue +// at (E+1, v=0) would collide on deterministic state ID with the first detached +// row. The handler instead appends after the detached tip at (E+1, M+1), +// inheriting the tip's ledger and adding the shortfall on top. +func TestHandleHomeChannelClosed_TimeoutAfterFinalize_AppendsRescue(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + closureVersion := uint64(5) // Y; Finalize was at a higher version F. + rescueAmount := decimal.NewFromInt(80) + priorDetachedBalance := decimal.NewFromInt(20) + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 4, + ChallengeExpiresAt: &expiryTime, + } + + // Detached tip: post-Finalize receiver at (E+1, v=3) with HomeChannelID nil. + detachedTip := &core.State{ + ID: core.GetStateID(userWallet, asset, 2, 3), + Asset: asset, + UserWallet: userWallet, + Epoch: 2, + Version: 3, + HomeChannelID: nil, + HomeLedger: core.Ledger{ + UserBalance: priorDetachedBalance, + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: priorDetachedBalance, + }, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: closureVersion, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.Status == core.ChannelStatusClosed && + ch.StateVersion == closureVersion && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, closureVersion, "", "").Return(nil) + mockStore.On("SumNetTransitionAmountAfterVersion", channelID, closureVersion).Return(rescueAmount, nil) + mockStore.On("GetLastUserState", userWallet, asset, false).Return(detachedTip, nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), "").Return(nil) + var capturedTx core.Transaction + mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + capturedTx = tx + return true + }), "").Return(nil) + + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + require.NoError(t, err) + + require.Equal(t, core.TransitionTypeChallengeRescue, capturedState.Transition.Type) + require.Equal(t, channelID, capturedState.Transition.AccountID) + require.True(t, rescueAmount.Equal(capturedState.Transition.Amount)) + require.Nil(t, capturedState.HomeChannelID, "rescue stays off-channel") + + // Append at (detachedTip.Epoch, detachedTip.Version+1), inheriting prior balance. + require.Equal(t, detachedTip.Epoch, capturedState.Epoch) + require.Equal(t, detachedTip.Version+1, capturedState.Version) + require.True(t, priorDetachedBalance.Add(rescueAmount).Equal(capturedState.HomeLedger.UserBalance), + "want %s, got %s", priorDetachedBalance.Add(rescueAmount).String(), capturedState.HomeLedger.UserBalance.String()) + require.True(t, priorDetachedBalance.Add(rescueAmount).Equal(capturedState.HomeLedger.NodeNetFlow)) + + require.Equal(t, core.TransactionTypeChallengeRescue, capturedTx.TxType) + require.Equal(t, channelID, capturedTx.FromAccount) + require.Equal(t, userWallet, capturedTx.ToAccount) + require.True(t, rescueAmount.Equal(capturedTx.Amount)) + + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelClosed_CooperativeCloseAfterChallenge_ZeroRescue pins the +// cooperative-close-after-local-challenge race: the operator counter-submitted a +// Finalize at version F while the channel was Challenged, the user then accepted +// cooperative CLOSE at the same version F, and the close event arrives with +// StateVersion == F. NextState() at sign time detached the post-Finalize chain at +// (E+1, v=0..M) with HomeChannelID nil. +// +// SumNetTransitionAmountAfterVersion(channelID, F) must collapse to zero by the +// SQL predicate, with no intent gate required: +// - In-channel rows live at versions <= F (closure version is the channel head). +// - Post-Finalize detached rows have home_channel_id NULL and are excluded by +// the channel_id equality predicate. +// +// The rescue must therefore emit a zero-amount state appended after the detached +// tip at (E+1, M+1), inheriting the tip's ledger unchanged — chain still advances +// so future receiver issuance and channels.v1.request_creation no longer wedge on +// the closed channel, but no extra balance is credited beyond what the detached +// chain already holds. This is the invariant that lets the unconditional rescue +// branch be safe without forwarding finalState.intent through the reactor. +func TestHandleHomeChannelClosed_CooperativeCloseAfterChallenge_ZeroRescue(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + asset := "USDC" + closureVersion := uint64(7) // F: cooperative CLOSE settles at the Finalize version. + priorDetachedBalance := decimal.NewFromInt(40) + expiryTime := time.Now().Add(time.Hour) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: asset, + Type: core.ChannelTypeHome, + Status: core.ChannelStatusChallenged, + StateVersion: 6, + ChallengeExpiresAt: &expiryTime, + } + + // Detached tip: post-Finalize receiver credits at (E+1, v=2) with HomeChannelID nil. + detachedTip := &core.State{ + ID: core.GetStateID(userWallet, asset, 2, 2), + Asset: asset, + UserWallet: userWallet, + Epoch: 2, + Version: 2, + HomeChannelID: nil, + HomeLedger: core.Ledger{ + UserBalance: priorDetachedBalance, + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: priorDetachedBalance, + }, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: closureVersion, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, asset).Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.Status == core.ChannelStatusClosed && + ch.StateVersion == closureVersion && + ch.ChallengeExpiresAt == nil + })).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, asset).Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, closureVersion, "", "").Return(nil) + // Key invariant: the predicate collapses cooperative CLOSE to a zero net. In-channel + // rows are at versions <= F, detached rows are excluded by home_channel_id = ?. + mockStore.On("SumNetTransitionAmountAfterVersion", channelID, closureVersion).Return(decimal.Zero, nil) + mockStore.On("GetLastUserState", userWallet, asset, false).Return(detachedTip, nil) + + var capturedState core.State + mockStore.On("StoreUserState", mock.MatchedBy(func(state core.State) bool { + capturedState = state + return true + }), "").Return(nil) + var capturedTx core.Transaction + mockStore.On("RecordTransaction", mock.MatchedBy(func(tx core.Transaction) bool { + capturedTx = tx + return true + }), "").Return(nil) + + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + require.NoError(t, err) + + require.Equal(t, core.TransitionTypeChallengeRescue, capturedState.Transition.Type) + require.Equal(t, channelID, capturedState.Transition.AccountID) + require.True(t, capturedState.Transition.Amount.IsZero(), + "cooperative CLOSE after challenge must produce zero-amount rescue, got %s", capturedState.Transition.Amount.String()) + require.Nil(t, capturedState.HomeChannelID, "rescue stays off-channel") + + // Append after the detached tip: (E, M+1), inheriting the tip's ledger unchanged. + require.Equal(t, detachedTip.Epoch, capturedState.Epoch) + require.Equal(t, detachedTip.Version+1, capturedState.Version) + require.True(t, priorDetachedBalance.Equal(capturedState.HomeLedger.UserBalance), + "balance must be unchanged from detached tip, want %s, got %s", + priorDetachedBalance.String(), capturedState.HomeLedger.UserBalance.String()) + require.True(t, priorDetachedBalance.Equal(capturedState.HomeLedger.NodeNetFlow)) + + require.Equal(t, core.TransactionTypeChallengeRescue, capturedTx.TxType) + require.Equal(t, channelID, capturedTx.FromAccount) + require.Equal(t, userWallet, capturedTx.ToAccount) + require.True(t, capturedTx.Amount.IsZero()) + + mockStore.AssertExpectations(t) +} + +// TestHandleHomeChannelClosed_OpenChannel_NoRescue pins behavior for the normal +// Open → Closed path: the channel was never Challenged before the close event, so the +// rescue branch is short-circuited entirely. The unsigned-receiver-sum query, rescue +// state store, and rescue transaction record are never issued. +func TestHandleHomeChannelClosed_OpenChannel_NoRescue(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service, _ := newTestEventHandlerService(t) + + channelID := "0xHomeChannel123" + userWallet := "0x1234567890123456789012345678901234567890" + closureVersion := uint64(7) + + channel := &core.Channel{ + ChannelID: channelID, + UserWallet: userWallet, + Asset: "USDC", + Type: core.ChannelTypeHome, + Status: core.ChannelStatusOpen, + StateVersion: 5, + } + + event := &core.HomeChannelClosedEvent{ + ChannelID: channelID, + StateVersion: closureVersion, + } + + mockStore.On("GetChannelByID", channelID).Return(channel, nil) + mockStore.On("LockUserState", userWallet, "USDC").Return(decimal.Zero, nil) + mockStore.On("UpdateChannel", mock.Anything).Return(nil) + mockStore.On("RefreshUserEnforcedBalance", userWallet, "USDC").Return(nil) + mockStore.On("UpdateStateSigsIfMissing", channelID, closureVersion, "", "").Return(nil) + + err := service.HandleHomeChannelClosed(ctx, mockStore, event) + require.NoError(t, err) + + mockStore.AssertExpectations(t) + mockStore.AssertNotCalled(t, "SumNetTransitionAmountAfterVersion", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "StoreUserState", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "RecordTransaction", mock.Anything, mock.Anything) +} + +func TestHandleUserLockedBalanceUpdated_StoreError(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{} + + // 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, mockStore, event) + + // Assert + require.Error(t, err) + require.Contains(t, err.Error(), "db error") + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowDepositsPurged_ClosesEscrowChannels(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + openEscrow := &core.Channel{ + ChannelID: "0xEscrow001", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusOpen, + } + // Already closed — UpdateChannel must NOT be called for it. + closedEscrow := &core.Channel{ + ChannelID: "0xEscrow002", + Type: core.ChannelTypeEscrow, + Status: core.ChannelStatusClosed, + } + + event := &core.EscrowDepositsPurgedEvent{ + EscrowIDs: []string{"0xEscrow001", "0xEscrow002", "0xUnknown003"}, + } + + mockStore.On("GetChannelByID", "0xEscrow001").Return(openEscrow, nil) + mockStore.On("UpdateChannel", mock.MatchedBy(func(ch core.Channel) bool { + return ch.ChannelID == "0xEscrow001" && ch.Status == core.ChannelStatusClosed + })).Return(nil) + mockStore.On("GetChannelByID", "0xEscrow002").Return(closedEscrow, nil) + mockStore.On("GetChannelByID", "0xUnknown003").Return(nil, nil) + + err := service.HandleEscrowDepositsPurged(ctx, mockStore, event) + + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleEscrowDepositsPurged_StoreError_Propagates(t *testing.T) { + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + service := &EventHandlerService{} + + event := &core.EscrowDepositsPurgedEvent{ + EscrowIDs: []string{"0xEscrow001"}, + } + + mockStore.On("GetChannelByID", "0xEscrow001").Return(nil, errors.New("db error")) + + err := service.HandleEscrowDepositsPurged(ctx, mockStore, event) + + require.Error(t, err) + require.Contains(t, err.Error(), "db error") + mockStore.AssertExpectations(t) +} diff --git a/nitronode/event_handlers/testing.go b/nitronode/event_handlers/testing.go new file mode 100644 index 000000000..dfe5be52f --- /dev/null +++ b/nitronode/event_handlers/testing.go @@ -0,0 +1,143 @@ +package event_handlers + +import ( + "github.com/shopspring/decimal" + "github.com/stretchr/testify/mock" + + "github.com/layer-3/nitrolite/pkg/core" +) + +// MockStore is a mock implementation of the Store interface for testing +type MockStore struct { + mock.Mock +} + +// GetLastStateByChannelID mocks retrieving the last state for a channel +func (m *MockStore) GetLastStateByChannelID(channelID string, signed bool) (*core.State, error) { + args := m.Called(channelID, signed) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.State), args.Error(1) +} + +// GetLastUserState mocks retrieving the most recent state for a user's asset across +// all channels and detached chain entries. +func (m *MockStore) GetLastUserState(wallet, asset string, signed bool) (*core.State, error) { + args := m.Called(wallet, asset, signed) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.State), args.Error(1) +} + +// GetStateByChannelIDAndVersion mocks retrieving a specific state version for a channel +func (m *MockStore) GetStateByChannelIDAndVersion(channelID string, version uint64) (*core.State, error) { + args := m.Called(channelID, version) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.State), args.Error(1) +} + +// UpdateChannel mocks updating a channel in the database +func (m *MockStore) UpdateChannel(channel core.Channel) error { + args := m.Called(channel) + return args.Error(0) +} + +// GetChannelByID mocks retrieving a channel by its ID +func (m *MockStore) GetChannelByID(channelID string) (*core.Channel, error) { + args := m.Called(channelID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.Channel), args.Error(1) +} + +// ScheduleCheckpoint mocks scheduling a checkpoint operation +func (m *MockStore) ScheduleCheckpoint(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +// ScheduleChallenge mocks scheduling a challenge operation +func (m *MockStore) ScheduleChallenge(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +// ScheduleInitiateEscrowDeposit mocks scheduling an escrow deposit checkpoint +func (m *MockStore) ScheduleInitiateEscrowDeposit(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +// ScheduleFinalizeEscrowDeposit mocks scheduling an escrow deposit checkpoint +func (m *MockStore) ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +// ScheduleFinalizeEscrowWithdrawal mocks scheduling an escrow withdrawal checkpoint +func (m *MockStore) ScheduleFinalizeEscrowWithdrawal(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +// SetNodeBalance mocks upserting the on-chain liquidity +func (m *MockStore) SetNodeBalance(blockchainID uint64, asset string, value decimal.Decimal) error { + args := m.Called(blockchainID, asset, value) + return args.Error(0) +} + +// RefreshUserEnforcedBalance mocks recomputing the locked balance from DB +func (m *MockStore) RefreshUserEnforcedBalance(wallet, asset string) error { + args := m.Called(wallet, asset) + 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) +} + +// UpdateStateSigsIfMissing mocks backfilling missing user and/or node signatures +// for a stored state. +func (m *MockStore) UpdateStateSigsIfMissing(channelID string, version uint64, userSig, nodeSig string) error { + args := m.Called(channelID, version, userSig, nodeSig) + return args.Error(0) +} + +// SumNetTransitionAmountAfterVersion mocks the net-change query used to compute +// challenge-rescue amounts on a closed channel. +func (m *MockStore) SumNetTransitionAmountAfterVersion(channelID string, minVersion uint64) (decimal.Decimal, error) { + args := m.Called(channelID, minVersion) + return args.Get(0).(decimal.Decimal), args.Error(1) +} + +// LockUserState mocks acquiring SELECT ... FOR UPDATE on a user's balance row. +func (m *MockStore) LockUserState(wallet, asset string) (decimal.Decimal, error) { + args := m.Called(wallet, asset) + return args.Get(0).(decimal.Decimal), args.Error(1) +} + +// HasSignedFinalize mocks the existence check for a node-signed Finalize state on the given home channel. +func (m *MockStore) HasSignedFinalize(channelID string) (bool, error) { + args := m.Called(channelID) + return args.Bool(0), args.Error(1) +} + + +// StoreUserState mocks persisting a user state row. +func (m *MockStore) StoreUserState(state core.State, applicationID string) error { + args := m.Called(state, applicationID) + return args.Error(0) +} + +// RecordTransaction mocks recording a transaction row. +func (m *MockStore) RecordTransaction(tx core.Transaction, applicationID string) error { + args := m.Called(tx, applicationID) + return args.Error(0) +} diff --git a/clearnode/main.go b/nitronode/main.go similarity index 59% rename from clearnode/main.go rename to nitronode/main.go index 3980fb3b1..74375ab50 100644 --- a/clearnode/main.go +++ b/nitronode/main.go @@ -11,13 +11,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ilyakaznacheev/cleanenv" "github.com/prometheus/client_golang/prometheus/promhttp" - "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/nitronode/api" + "github.com/layer-3/nitrolite/nitronode/event_handlers" + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/nitronode/store/database" + "github.com/layer-3/nitrolite/nitronode/store/memory" + "github.com/layer-3/nitrolite/nitronode/stress" "github.com/layer-3/nitrolite/pkg/blockchain/evm" "github.com/layer-3/nitrolite/pkg/core" "github.com/layer-3/nitrolite/pkg/log" @@ -43,11 +45,14 @@ func main() { rpcRouterCfg := api.RPCRouterConfig{ NodeVersion: bb.NodeVersion, MinChallenge: bb.ChannelMinChallengeDuration, + MaxChallenge: bb.ChannelMaxChallengeDuration, + AppRegistryEnabled: bb.AppRegistryEnabled, MaxParticipants: vl.MaxParticipants, MaxSessionDataLen: vl.MaxSessionDataLen, MaxAppMetadataLen: vl.MaxAppMetadataLen, MaxRebalanceSignedUpdates: vl.MaxSignedUpdates, MaxSessionKeyIDs: vl.MaxSessionKeyIDs, + MaxSessionKeysPerUser: vl.MaxSessionKeysPerUser, RateLimitPerSec: bb.RateLimitPerSec, RateLimitBurst: bb.RateLimitBurst, } @@ -71,11 +76,14 @@ func main() { wrapInTx := func(handler func(database.DatabaseStore) error) error { return bb.DbStore.ExecuteInTransaction(handler) } - useEHV1StoreInTx := func(h event_handlers.StoreTxHandler) error { - return wrapInTx(func(s database.DatabaseStore) error { return h(s) }) + + nodeChannelSigner, err := core.NewChannelDefaultSigner(bb.StateSigner) + if err != nil { + logger.Fatal("failed to build node channel signer", "error", err) } - eventHandlerService := event_handlers.NewEventHandlerService(useEHV1StoreInTx, logger) + eventHandlerStatePacker := core.NewStatePackerV1(bb.MemoryStore) + eventHandlerService := event_handlers.NewEventHandlerService(nodeChannelSigner, eventHandlerStatePacker) for _, b := range blockchains { rpcURL, ok := bb.BlockchainRPCs[b.ID] @@ -95,6 +103,7 @@ func main() { clientOpts := []evm.ClientOption{ evm.ClientBalanceCheck{RequireBalanceCheck: false}, evm.ClientAllowanceCheck{RequireAllowanceCheck: false}, + evm.ClientGasLimit{GasLimit: bb.BlockchainGasLimit}, } blockchainClient, err := evm.NewBlockchainClient(common.HexToAddress(b.ChannelHubAddress), client, bb.TxSigner, b.ID, nodeAddress, bb.MemoryStore, clientOpts...) @@ -111,16 +120,20 @@ func main() { logger.Fatal("failed to ensure signature validators are registered", "error", err, "blockchainID", b.ID) } - reactor := evm.NewChannelHubReactor(b.ID, eventHandlerService, bb.DbStore.StoreContractEvent) + useCHRStoreInTx := func(h evm.ChannelHubReactorStoreTxHandler) error { + return wrapInTx(func(s database.DatabaseStore) error { return h(s) }) + } + + reactor := evm.NewChannelHubReactor(b.ID, bb.StateSigner.PublicKey().Address().String(), eventHandlerService, bb.MemoryStore, useCHRStoreInTx) reactor.SetOnEventProcessed(bb.RuntimeMetrics.IncBlockchainEvent) - l := evm.NewListener(common.HexToAddress(b.ChannelHubAddress), client, b.ID, b.BlockStep, logger, reactor.HandleEvent, bb.DbStore.GetLatestEvent) + l := evm.NewListener(common.HexToAddress(b.ChannelHubAddress), client, b.ID, b.BlockStep, logger, reactor.HandleEvent, bb.DbStore) 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 := NewBlockchainWorker(b.ID, blockchainClient, bb.DbStore, nodeChannelSigner, bb.MemoryStore, logger, bb.RuntimeMetrics) worker.Start(blockchainCtx, func(err error) { if err != nil { logger.Fatal("blockchain worker stopped", "error", err, "blockchainID", b.ID) @@ -136,13 +149,17 @@ func main() { logger.Fatal("failed to create locking client", "error", err, "blockchainID", b.ID) } - reactor, err := evm.NewLockingContractReactor(b.ID, eventHandlerService, appRegistryClient.GetTokenDecimals, bb.DbStore.StoreContractEvent) + useLCRStoreInTx := func(h evm.LockingContractReactorStoreTxHandler) error { + return wrapInTx(func(s database.DatabaseStore) error { return h(s) }) + } + + reactor, err := evm.NewLockingContractReactor(b.ID, eventHandlerService, appRegistryClient.GetTokenDecimals, useLCRStoreInTx) if err != nil { 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 := evm.NewListener(common.HexToAddress(b.LockingContractAddress), client, b.ID, b.BlockStep, logger, reactor.HandleEvent, bb.DbStore) l.Listen(blockchainCtx, func(err error) { if err != nil { logger.Fatal("blockchain listener stopped", "error", err, "blockchainID", b.ID) @@ -214,7 +231,7 @@ func main() { func runOperatorCommand(args []string) { if len(args) == 0 { - fmt.Println("Usage: clearnode operator ") + fmt.Println("Usage: nitronode operator ") fmt.Println("Commands:") fmt.Println(" address Print the operator address") fmt.Println(" register-validators Register signature validators on-chain") @@ -233,19 +250,47 @@ func runOperatorCommand(args []string) { } func runOperatorAddress() { - bb := InitBackbone() - defer bb.Close() + logger := initLogger() + _ = initBase(logger) + + var signerConf SignerConfig + if err := cleanenv.ReadEnv(&signerConf); err != nil { + logger.Fatal("failed to read signer configuration from env", "error", err) + } + + signer, close := initSigner( + logger, + signerConf.Type, + signerConf.Key, + signerConf.GCPKMSKeyName) + defer close() - fmt.Println(bb.StateSigner.PublicKey().Address().String()) - time.Sleep(5 * time.Second) + logger.Info("node signer initialized", "address", signer.PublicKey().Address().String(), "signerType", signerConf.Type) } func runRegisterValidators() { - bb := InitBackbone() - defer bb.Close() - logger := bb.Logger + logger := initLogger() + configDirPath := initBase(logger) - blockchains, err := bb.MemoryStore.GetBlockchains() + memoryStore, err := memory.NewMemoryStoreV1FromConfig(configDirPath) + if err != nil { + logger.Fatal("failed to load blockchains", "error", err) + } + + var conf FullConfig + if err := cleanenv.ReadEnv(&conf); err != nil { + logger.Fatal("failed to read configuration from env", "error", err) + } + signer, close := initSigner( + logger, + conf.Signer.Type, + conf.Signer.Key, + conf.Signer.GCPKMSKeyName) + defer close() + + blockchainRPCs := initBlockchainRPCs(logger, memoryStore) + + blockchains, err := memoryStore.GetBlockchains() if err != nil { logger.Fatal("failed to get blockchains from memory store", "error", err) } @@ -255,7 +300,7 @@ func runRegisterValidators() { continue } - rpcURL, ok := bb.BlockchainRPCs[b.ID] + rpcURL, ok := blockchainRPCs[b.ID] if !ok { logger.Fatal("no RPC URL configured for blockchain", "blockchainID", b.ID) } @@ -265,18 +310,19 @@ func runRegisterValidators() { logger.Fatal("failed to connect to EVM Node", "blockchainID", b.ID) } - nodeAddress := bb.StateSigner.PublicKey().Address().String() + nodeAddress := signer.PublicKey().Address().String() clientOpts := []evm.ClientOption{ evm.ClientBalanceCheck{RequireBalanceCheck: false}, evm.ClientAllowanceCheck{RequireAllowanceCheck: false}, + evm.ClientGasLimit{GasLimit: conf.BlockchainGasLimit}, } - blockchainClient, err := evm.NewBlockchainClient(common.HexToAddress(b.ChannelHubAddress), client, bb.TxSigner, b.ID, nodeAddress, bb.MemoryStore, clientOpts...) + blockchainClient, err := evm.NewBlockchainClient(common.HexToAddress(b.ChannelHubAddress), client, signer, b.ID, nodeAddress, memoryStore, clientOpts...) if err != nil { logger.Fatal("failed to create EVM client", "blockchainID", b.ID) } - sigValidators, err := bb.MemoryStore.GetChannelSigValidators(b.ID) + sigValidators, err := memoryStore.GetChannelSigValidators(b.ID) if err != nil { logger.Fatal("failed to get channel signature validators from memory store", "error", err, "blockchainID", b.ID) } @@ -308,6 +354,8 @@ func runStoreMetricsExporter( GetChannelsCountByLabels() ([]database.ChannelCount, error) GetAppSessionsCountByLabels() ([]database.AppSessionCount, error) GetTotalValueLocked() ([]database.TotalValueLocked, error) + GetNodeBalance() ([]database.NodeBalance, error) + GetUserBalanceSummary() ([]database.UserBalanceSummary, error) CountActiveUsers(window time.Duration) ([]database.ActiveCountByLabel, error) CountActiveAppSessions(window time.Duration) ([]database.ActiveCountByLabel, error) }, @@ -316,65 +364,107 @@ func runStoreMetricsExporter( ticker := time.NewTicker(fetchInterval) defer ticker.Stop() - for { - select { - case <-ticker.C: - timeSpans := []struct { - label string - duration time.Duration - }{ - {"day", 24 * time.Hour}, - {"week", 7 * 24 * time.Hour}, - {"month", 30 * 24 * time.Hour}, + timeSpans := []struct { + label string + duration time.Duration + }{ + {"day", 24 * time.Hour}, + {"week", 7 * 24 * time.Hour}, + {"month", 30 * 24 * time.Hour}, + } + + // refresh runs one full pass over every store-backed gauge. Each block resets + // its gauge before re-publishing the latest snapshot so label tuples that have + // dropped out of the store (e.g., the last open channel for an asset closed) + // disappear from the metric instead of sticking at their previous value. Reset + // happens only inside the successful-query branch so a transient DB error does + // not blank a healthy gauge. + refresh := func() { + channelCounts, err := store.GetChannelsCountByLabels() + if err != nil { + logger.Error("failed to get channel counts by labels", "error", err) + } else { + metricExported.ResetChannels() + for _, c := range channelCounts { + metricExported.SetChannels(c.Asset, c.Status, c.Count) } + } - 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 { + metricExported.ResetAppSessions() + for _, c := range appSessionCounts { + metricExported.SetAppSessions(c.Application, 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 appSessionCounts { - metricExported.SetAppSessions(c.Application, c.Status, c.Count) - } + tvlCounts, err := store.GetTotalValueLocked() + if err != nil { + logger.Error("failed to get total value locked", "error", err) + } else { + metricExported.ResetTotalValueLocked() + for _, c := range tvlCounts { + metricExported.SetTotalValueLocked(c.Domain, c.Asset, c.Value.InexactFloat64()) } + } - tvlCounts, err := store.GetTotalValueLocked() - if err != nil { - logger.Error("failed to get total value locked", "error", err) - } else { - for _, c := range tvlCounts { - metricExported.SetTotalValueLocked(c.Domain, c.Asset, c.Value.InexactFloat64()) - } + nodeBalances, err := store.GetNodeBalance() + if err != nil { + logger.Error("failed to get node balances", "error", err) + } else { + metricExported.ResetNodeBalance() + for _, nb := range nodeBalances { + metricExported.SetNodeBalance(nb.BlockchainID, nb.Asset, nb.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) - } - } + offChain, err := store.GetUserBalanceSummary() + if err != nil { + logger.Error("failed to get off-chain liquidity", "error", err) + } else { + metricExported.ResetUserBalances() + for _, l := range offChain { + metricExported.SetUserBalanceTotal(l.BlockchainID, l.Asset, l.Total.InexactFloat64()) + metricExported.SetUserBalanceUnderfunded(l.BlockchainID, l.Asset, l.Underfunded.InexactFloat64()) + metricExported.SetUserBalanceReleasable(l.BlockchainID, l.Asset, l.Releasable.InexactFloat64()) } + } - 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) - } - } + for _, tw := range timeSpans { + counts, err := store.CountActiveUsers(tw.duration) + if err != nil { + logger.Error("failed to count active users", "timeframe", tw.label, "error", err) + continue + } + metricExported.ResetActiveUsers(tw.label) + for _, c := range counts { + metricExported.SetActiveUsers(c.Label, tw.label, c.Count) } + } + for _, tw := range timeSpans { + counts, err := store.CountActiveAppSessions(tw.duration) + if err != nil { + logger.Error("failed to count active app sessions", "timeframe", tw.label, "error", err) + continue + } + metricExported.ResetActiveAppSessions(tw.label) + for _, c := range counts { + metricExported.SetActiveAppSessions(c.Label, tw.label, c.Count) + } + } + } + + // Populate gauges immediately so they are non-empty before the first ticker fire + // (otherwise alerts using absent() trip during the cold-start window). + refresh() + + for { + select { + case <-ticker.C: + refresh() case <-ctx.Done(): logger.Info("stopping store metrics exporter") return diff --git a/nitronode/metrics/exporter.go b/nitronode/metrics/exporter.go new file mode 100644 index 000000000..a95f8319a --- /dev/null +++ b/nitronode/metrics/exporter.go @@ -0,0 +1,577 @@ +package metrics + +import ( + "fmt" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +const ( + // MetricNamespace is the common namespace for all Nitronode metrics. + // Renamed from "clearnode" in v1.3.0 — Prometheus alerts and Grafana + // dashboards must be updated to reference nitronode_* metric names. + MetricNamespace = "nitronode" +) + +// allActionResults is the closed enum of result label values used by counters +// whose outcome dimension is success/failed. +var allActionResults = []ActionResult{ActionResultSuccess, ActionResultFailed} + +var ( + _ RuntimeMetricExporter = (*runtimeMetricExporter)(nil) + _ StoreMetricExporter = (*storeMetricExporter)(nil) +) + +type storeMetricExporter struct { + appSessionsTotal *prometheus.GaugeVec + channelsTotal *prometheus.GaugeVec + usersActive *prometheus.GaugeVec + appSessionsActive *prometheus.GaugeVec + totalValueLocked *prometheus.GaugeVec + nodeBalance *prometheus.GaugeVec + userBalanceTotal *prometheus.GaugeVec + userBalanceUnderfunded *prometheus.GaugeVec + userBalanceReleasable *prometheus.GaugeVec +} + +func NewStoreMetricExporter(reg prometheus.Registerer) (StoreMetricExporter, error) { + m := &storeMetricExporter{ + appSessionsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "app_sessions_total", + Help: "Current count of app sessions, refreshed by the periodic store " + + "ticker (~1 min). Labels: application_id (self-declared by clients, " + + "empty becomes \"_DEFAULT\"), status ∈ {\"\" (void), open, closed}. " + + "Note: void renders as empty-string label value — sum by (status) " + + "will produce a series with status=\"\".", + }, []string{"application_id", "status"}), + channelsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "channels_total", + Help: "Current count of payment channels, refreshed by the periodic store " + + "ticker (~1 min). Labels: asset (e.g. usdc, eth), " + + "status ∈ {void, open, challenged, closed}.", + }, []string{"asset", "status"}), + usersActive: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "users_active", + Help: "Active users per asset over a rolling time window. Labels: asset, " + + "timespan ∈ {day, week, month}. Each window is computed independently " + + "from the store; values are not strictly nested (a user active in the " + + "day window also counts in week + month).", + }, []string{"asset", "timespan"}), + appSessionsActive: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "app_sessions_active", + Help: "Active app sessions per application over a rolling time window. " + + "Labels: application_id, timespan ∈ {day, week, month}.", + }, []string{"application_id", "timespan"}), + totalValueLocked: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "total_value_locked", + Help: "Total value locked across all channels and app-sessions, in the " + + "asset's native units. Labels: domain (business unit), asset. Sum " + + "across assets is meaningless without a price feed.", + }, []string{"domain", "asset"}), + nodeBalance: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "node_balance", + Help: "Operator's on-chain balance per (blockchain, asset). This is the " + + "liquidity pool the node owns directly — separate from channel " + + "balances held by users (see user_balance_total). Native asset units.", + }, []string{"blockchain_id", "asset"}), + userBalanceTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "user_balance_total", + Help: "Sum of user channel balances per (blockchain, asset). These funds " + + "live off-chain in user channels — they are NOT a subset of " + + "node_balance, the two are parallel pools. Native asset units.", + }, []string{"blockchain_id", "asset"}), + userBalanceUnderfunded: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "user_balance_underfunded", + Help: "Worst-case potential outflow from node_balance per (blockchain, " + + "asset): if every underfunded channel checkpointed simultaneously " + + "with its latest state, this much would move from on-chain (node) to " + + "user channels. Liquidity health: compare against node_balance per " + + "pool — sustained underfunded > node_balance is paging-grade. Native " + + "asset units.", + }, []string{"blockchain_id", "asset"}), + userBalanceReleasable: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "user_balance_releasable", + Help: "Worst-case potential inflow to node_balance per (blockchain, " + + "asset): if every releasable channel checkpointed simultaneously, " + + "this much would move from user channels back into on-chain. " + + "Native asset units.", + }, []string{"blockchain_id", "asset"}), + } + + if reg != nil { + reg.MustRegister( + m.appSessionsTotal, + m.channelsTotal, + m.usersActive, + m.appSessionsActive, + m.totalValueLocked, + m.nodeBalance, + m.userBalanceTotal, + m.userBalanceUnderfunded, + m.userBalanceReleasable, + ) + } else { + return nil, fmt.Errorf("prometheus registerer not provided") + } + + return m, nil +} + +func (m *storeMetricExporter) SetAppSessions(applicationID string, status app.AppSessionStatus, count uint64) { + m.appSessionsTotal.WithLabelValues(getApplicationIDLabelValue(applicationID), status.String()).Set(float64(count)) +} + +func (m *storeMetricExporter) SetChannels(asset string, status core.ChannelStatus, count uint64) { + 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(getApplicationIDLabelValue(applicationID), timeSpanLabel).Set(float64(count)) +} + +func (m *storeMetricExporter) SetTotalValueLocked(domain, asset string, value float64) { + m.totalValueLocked.WithLabelValues(domain, asset).Set(value) +} + +func (m *storeMetricExporter) SetNodeBalance(blockchainID, asset string, value float64) { + m.nodeBalance.WithLabelValues(blockchainID, asset).Set(value) +} + +func (m *storeMetricExporter) SetUserBalanceTotal(blockchainID, asset string, value float64) { + m.userBalanceTotal.WithLabelValues(blockchainID, asset).Set(value) +} + +func (m *storeMetricExporter) SetUserBalanceUnderfunded(blockchainID, asset string, value float64) { + m.userBalanceUnderfunded.WithLabelValues(blockchainID, asset).Set(value) +} + +func (m *storeMetricExporter) SetUserBalanceReleasable(blockchainID, asset string, value float64) { + m.userBalanceReleasable.WithLabelValues(blockchainID, asset).Set(value) +} + +func (m *storeMetricExporter) ResetAppSessions() { m.appSessionsTotal.Reset() } +func (m *storeMetricExporter) ResetChannels() { m.channelsTotal.Reset() } +func (m *storeMetricExporter) ResetTotalValueLocked() { m.totalValueLocked.Reset() } +func (m *storeMetricExporter) ResetNodeBalance() { m.nodeBalance.Reset() } +func (m *storeMetricExporter) ResetUserBalances() { + m.userBalanceTotal.Reset() + m.userBalanceUnderfunded.Reset() + m.userBalanceReleasable.Reset() +} + +// ResetActiveUsers clears only the rows for the given timespan so a failure in one +// window does not blank the gauges for windows that may still be succeeding. +func (m *storeMetricExporter) ResetActiveUsers(timeSpanLabel string) { + m.usersActive.DeletePartialMatch(prometheus.Labels{"timespan": timeSpanLabel}) +} + +// ResetActiveAppSessions clears only the rows for the given timespan; see ResetActiveUsers. +func (m *storeMetricExporter) ResetActiveAppSessions(timeSpanLabel string) { + m.appSessionsActive.DeletePartialMatch(prometheus.Labels{"timespan": timeSpanLabel}) +} + +// runtimeMetricExporter is the concrete implementation of the Metrics interface. +type runtimeMetricExporter struct { + // Shared Metrics (Cross-Package) + userStatesTotal *prometheus.CounterVec + transactionsTotal *prometheus.CounterVec + transactionsAmountTotal *prometheus.CounterVec + channelStateSigValidationsTotal *prometheus.CounterVec + + // api/rpc_router.go + rpcMessagesTotal *prometheus.CounterVec + rpcRequestsTotal *prometheus.CounterVec + rpcRequestDurationSeconds *prometheus.HistogramVec + rpcConnectionsTotal *prometheus.GaugeVec + rpcInflight *prometheus.GaugeVec + + // api/app_session_v1 + appStateUpdates *prometheus.CounterVec + appSessionUpdateSigValidationsTotal *prometheus.CounterVec + + // Blockchain Worker + blockchainActionsTotal *prometheus.CounterVec + + // Event Listener + blockchainEventsTotal *prometheus.CounterVec + + // Metric Worker + channelSessionKeysTotal prometheus.Counter + appSessionKeysTotal prometheus.Counter + + // store/database (instrumented via gorm callbacks) + dbQueryDurationSeconds *prometheus.HistogramVec +} + +// RuntimeMetricExporter exposes metrics related to runtime operations, such as API requests, channel state validations, and blockchain interactions. +// +// Cardinality note: user_states_total, transactions_total and +// transactions_amount_total carry an application_id label. The value is +// self-declared by clients and bounded only by the ingress regex +// (pkg/app.ApplicationIDRegex, ~66 chars of [a-z0-9_-]). Operators running +// untrusted clients should monitor series count on these metrics and, if +// needed, add an allowlist or recording-rule aggregation before ingest. +func NewRuntimeMetricExporter(reg prometheus.Registerer) (RuntimeMetricExporter, error) { + m := &runtimeMetricExporter{ + // Shared + userStatesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "user_states_total", + Help: "Total state transitions recorded for users. Labels: asset, " + + "home_blockchain_id (uint64 stringified), transition (state " + + "transition kind, see core.TransitionType — void / acknowledgement " + + "/ transfer_send / transfer_receive / commit / release / " + + "home_deposit / home_withdrawal / mutual_lock / escrow_deposit / " + + "escrow_lock / escrow_withdraw / migrate / finalize), " + + "application_id (\"_DEFAULT\" when client did not supply one).", + }, []string{"asset", "home_blockchain_id", "transition", "application_id"}), + transactionsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "transactions_total", + Help: "Total transactions recorded. Labels: asset, tx_type (see " + + "core.TransactionType — transfer / release / commit / home_deposit " + + "/ home_withdrawal / mutual_lock / escrow_deposit / escrow_lock / " + + "escrow_withdraw / migrate / rebalance / finalize), application_id. " + + "Pair with transactions_amount_total for value-weighted views.", + }, []string{"asset", "tx_type", "application_id"}), + transactionsAmountTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "transactions_amount_total", + Help: "Cumulative transaction amounts in native asset units. Same labels " + + "as transactions_total. Note: amounts are aggregated via " + + "decimal.Decimal.InexactFloat64() — for very large or very precise " + + "values this is lossy. For accounting accuracy query the database " + + "directly; this metric is for dashboards and alerts only.", + }, []string{"asset", "tx_type", "application_id"}), + channelSessionKeysTotal: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "channel_session_keys_total", + Help: "Total channel session keys issued by the node. Unlabelled. Use " + + "rate(channel_session_keys_total[5m]) for issuance rate.", + }), + appSessionKeysTotal: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "app_session_keys_total", + Help: "Total app session keys issued by the node. Unlabelled. Use " + + "rate(app_session_keys_total[5m]) for issuance rate.", + }), + channelStateSigValidationsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "channel_state_sig_validations_total", + Help: "Channel-state signature validations attempted. Labels: " + + "sig_type ∈ {default (wallet-signed), session_key}, " + + "result ∈ {success, failed}. " + + "For error rate compute " + + "rate(...{result=\"failed\"}[5m]) / rate(...[5m]); failed includes " + + "both forgery attempts and operational bugs.", + }, []string{"sig_type", "result"}), + + // api/rpc_router + rpcMessagesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "rpc_messages_emitted_total", + Help: "Total number of RPC messages emitted (request + response). Counter increments twice per request — once for the request msg and once for the response — so rate(...) over this is roughly 2× the QPS. For real RPC throughput use rpc_requests_total.", + }, []string{"msg_type", "method"}), + rpcRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "rpc_requests_total", + Help: "Total top-level RPC requests handled. Increments once per " + + "method invocation (not per WebSocket message). Labels: " + + "method (RPC method name), path (request path / group), " + + "result ∈ {success, failed}. " + + "\"success\" = response message type is Resp; everything else " + + "(RespErr, Event, no response) maps to failed.", + }, []string{"method", "path", "result"}), + rpcRequestDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: MetricNamespace, + Name: "rpc_request_duration_seconds", + Help: "RPC request duration in seconds. Same labels as " + + "rpc_requests_total. Use histogram_quantile for P50/P95/P99 " + + "latency; pair with rpc_inflight to distinguish slow handlers " + + "from queued contention.", + Buckets: []float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, + }, []string{"method", "path", "result"}), + rpcConnectionsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "rpc_connections_active", + Help: "Active RPC (WebSocket) connections, labeled by application_id " + + "sourced from the app_id query parameter at connect time. " + + "Connections without an app_id are bucketed under _DEFAULT. " + + "Series for an application_id are deleted once its count drops to 0. " + + "NOTE: cardinality is bounded by the app_id format check " + + "(^[a-z0-9_-]{1,66}$) and by series shedding on disconnect — long-lived " + + "connections from many distinct but format-valid app_ids are not gated " + + "by a registry or per-app connection cap.", + }, []string{"application_id"}), + rpcInflight: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "rpc_inflight", + Help: "Currently in-flight RPC requests, labelled by method. Incremented at " + + "middleware entry and decremented on exit. Saturation signal — pair with " + + "rpc_request_duration_seconds for queueing-style diagnosis.", + }, []string{"method"}), + + // api/app_session_v1 + appStateUpdates: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "app_state_updates_total", + Help: "Total app-session state updates accepted. Label: " + + "application_id (\"_DEFAULT\" when client did not supply one).", + }, []string{"application_id"}), + appSessionUpdateSigValidationsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "app_session_update_sig_validations_total", + Help: "App-session update signature validations attempted. Labels: " + + "application_id, sig_type ∈ {wallet, session_key}, " + + "result ∈ {success, failed}.", + }, []string{"application_id", "sig_type", "result"}), + + // Blockchain Worker + blockchainActionsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "blockchain_actions_total", + Help: "Blockchain actions dispatched by the node (deposits, withdrawals, " + + "escrow ops, etc.). Labels: asset, blockchain_id (uint64 stringified), " + + "action_type (uses core.TransitionType.String()), " + + "result ∈ {success, failed}.", + }, []string{"asset", "blockchain_id", "action_type", "result"}), + + // Event Listener + blockchainEventsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricNamespace, + Name: "blockchain_events_total", + Help: "On-chain events received and processed by the listener. Labels: " + + "blockchain_id (uint64 stringified), result ∈ {success, failed}. " + + "A flat / zero rate per chain may indicate a stalled chain " + + "connection — pair with chain-side liveness checks.", + }, []string{"blockchain_id", "result"}), + + // store/database + dbQueryDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: MetricNamespace, + Name: "db_query_duration_seconds", + Help: "Application-side database query duration. Observed via gorm " + + "callbacks, so the value is round-trip time from the app to the DB " + + "through pgbouncer (when used). Pair with go_sql_wait_duration_" + + "seconds_total / go_sql_wait_count_total (emitted by the DB-stats " + + "collector registered alongside) to separate pool-acquire latency " + + "from in-DB execution.\n\n" + + " query_kind — gorm operation: create, query, update, delete, " + + "row, raw.", + Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5}, + }, []string{"query_kind"}), + } + + if reg != nil { + reg.MustRegister( + m.userStatesTotal, + m.transactionsTotal, + m.transactionsAmountTotal, + m.channelStateSigValidationsTotal, + m.rpcMessagesTotal, + m.rpcRequestsTotal, + m.rpcRequestDurationSeconds, + m.rpcConnectionsTotal, + m.rpcInflight, + m.appStateUpdates, + m.appSessionUpdateSigValidationsTotal, + m.blockchainActionsTotal, + m.blockchainEventsTotal, + m.channelSessionKeysTotal, + m.appSessionKeysTotal, + m.dbQueryDurationSeconds, + ) + } else { + return nil, fmt.Errorf("prometheus registerer not provided") + } + + // Seed counters whose label space is fully resolved at construction time. + // Router and chain axes are seeded later via SeedRPCMethodMetrics / + // SeedBlockchainEventMetrics once that config is loaded. + for _, st := range core.ChannelSignerTypes { + for _, res := range allActionResults { + m.channelStateSigValidationsTotal.WithLabelValues(st.String(), res.String()) + } + } + + return m, nil +} + +// SeedRPCMethodMetrics publishes 0-valued series for every (msg_type, method), +// (method, path, result) and (method) tuple, so per-method PromQL queries return +// defined values immediately after boot. The path domain per method comes from +// methodPaths; methods absent from the map (or mapped to an empty slice) get +// path="default" only. Methods whose request payload determines path should be +// listed in methodPaths with their full bounded enum, so absent()-style alerts +// don't flap on the first request of each variant. +func (m *runtimeMetricExporter) SeedRPCMethodMetrics(methods []string, methodPaths map[string][]string) { + msgTypes := []rpc.MsgType{rpc.MsgTypeReq, rpc.MsgTypeResp, rpc.MsgTypeRespErr} + const defaultPath = "default" + for _, method := range methods { + for _, mt := range msgTypes { + m.rpcMessagesTotal.WithLabelValues(mt.String(), method) + } + paths := methodPaths[method] + if len(paths) == 0 { + paths = []string{defaultPath} + } + for _, p := range paths { + for _, res := range allActionResults { + m.rpcRequestsTotal.WithLabelValues(method, p, res.String()) + m.rpcRequestDurationSeconds.WithLabelValues(method, p, res.String()) + } + } + m.rpcInflight.WithLabelValues(method) + } +} + +// SeedBlockchainEventMetrics publishes 0-valued series for every (blockchain_id, +// result) tuple, so a chain whose listener is wired but has not yet observed an +// event still appears in queries — including absent()/rate() alerts watching for +// stalled connections. +func (m *runtimeMetricExporter) SeedBlockchainEventMetrics(blockchainIDs []uint64) { + for _, id := range blockchainIDs { + idStr := strconv.FormatUint(id, 10) + for _, res := range allActionResults { + m.blockchainEventsTotal.WithLabelValues(idStr, res.String()) + } + } +} + +// Shared +func (m *runtimeMetricExporter) IncUserState(asset string, homeBlockchainID uint64, transition core.TransitionType, applicationID string) { + homeBlockchainIDStr := strconv.FormatUint(homeBlockchainID, 10) + m.userStatesTotal.WithLabelValues(asset, homeBlockchainIDStr, transition.String(), getApplicationIDLabelValue(applicationID)).Inc() +} + +func (m *runtimeMetricExporter) RecordTransaction(asset string, txType core.TransactionType, amount decimal.Decimal, applicationID string) { + appIDLabelValue := getApplicationIDLabelValue(applicationID) + m.transactionsTotal.WithLabelValues(asset, txType.String(), appIDLabelValue).Inc() + m.transactionsAmountTotal.WithLabelValues(asset, txType.String(), appIDLabelValue).Add(amount.InexactFloat64()) +} + +// api/rpc_router +func (m *runtimeMetricExporter) IncRPCMessage(msgType rpc.MsgType, method string) { + m.rpcMessagesTotal.WithLabelValues(msgType.String(), method).Inc() +} + +func (m *runtimeMetricExporter) IncRPCRequest(method, path string, success bool) { + result := ActionResultFailed + if success { + result = ActionResultSuccess + } + m.rpcRequestsTotal.WithLabelValues(method, path, result.String()).Inc() +} + +func (m *runtimeMetricExporter) ObserveRPCDuration(method, path string, success bool, duration time.Duration) { + result := ActionResultFailed + if success { + result = ActionResultSuccess + } + m.rpcRequestDurationSeconds.WithLabelValues(method, path, result.String()).Observe(duration.Seconds()) +} + +func (m *runtimeMetricExporter) SetRPCConnections(applicationID string, count uint32) { + label := getApplicationIDLabelValue(applicationID) + if count == 0 { + // Shed the series when the bucket empties so unique application_id values + // from clients cannot accumulate unbounded gauge labels over time. + m.rpcConnectionsTotal.DeleteLabelValues(label) + return + } + m.rpcConnectionsTotal.WithLabelValues(label).Set(float64(count)) +} + +func (m *runtimeMetricExporter) IncRPCInflight(method string) { + m.rpcInflight.WithLabelValues(method).Inc() +} + +func (m *runtimeMetricExporter) DecRPCInflight(method string) { + m.rpcInflight.WithLabelValues(method).Dec() +} + +// store/database +func (m *runtimeMetricExporter) ObserveDBQueryDuration(queryKind string, duration time.Duration) { + m.dbQueryDurationSeconds.WithLabelValues(queryKind).Observe(duration.Seconds()) +} + +// api/app_session_v1 +func (m *runtimeMetricExporter) IncAppStateUpdate(applicationID string) { + m.appStateUpdates.WithLabelValues(getApplicationIDLabelValue(applicationID)).Inc() +} + +func (m *runtimeMetricExporter) IncAppSessionUpdateSigValidation(applicationID string, signerType app.AppSessionSignerTypeV1, result bool) { + res := ActionResultFailed + if result { + res = ActionResultSuccess + } + m.appSessionUpdateSigValidationsTotal.WithLabelValues(getApplicationIDLabelValue(applicationID), signerType.String(), res.String()).Inc() +} + +func (m *runtimeMetricExporter) IncChannelStateSigValidation(sigType core.ChannelSignerType, result bool) { + res := ActionResultFailed + if result { + res = ActionResultSuccess + } + m.channelStateSigValidationsTotal.WithLabelValues(sigType.String(), res.String()).Inc() +} + +// Blockchain Worker +func (m *runtimeMetricExporter) IncBlockchainAction(asset string, blockchainID uint64, actionType string, result bool) { + stringBlockchainID := strconv.FormatUint(blockchainID, 10) + res := ActionResultFailed + if result { + res = ActionResultSuccess + } + m.blockchainActionsTotal.WithLabelValues(asset, stringBlockchainID, actionType, res.String()).Inc() +} + +// Event Listener +func (m *runtimeMetricExporter) IncBlockchainEvent(blockchainID uint64, result bool) { + stringBlockchainID := strconv.FormatUint(blockchainID, 10) + res := ActionResultFailed + if result { + res = ActionResultSuccess + } + m.blockchainEventsTotal.WithLabelValues(stringBlockchainID, res.String()).Inc() +} + +// Metric Worker +func (m *runtimeMetricExporter) IncChannelSessionKeys() { + m.channelSessionKeysTotal.Inc() +} + +func (m *runtimeMetricExporter) IncAppSessionKeys() { + m.appSessionKeysTotal.Inc() +} + +type ActionResult string + +const ( + ActionResultSuccess ActionResult = "success" + ActionResultFailed ActionResult = "failed" +) + +func (res ActionResult) String() string { + return string(res) +} diff --git a/nitronode/metrics/exporter_test.go b/nitronode/metrics/exporter_test.go new file mode 100644 index 000000000..94665ac4e --- /dev/null +++ b/nitronode/metrics/exporter_test.go @@ -0,0 +1,379 @@ +package metrics + +import ( + "sort" + "strconv" + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" +) + +// labelSet renders a metric's labels as a stable "k=v,k=v" string for assertion. +func labelSet(m *dto.Metric) string { + pairs := make([]string, 0, len(m.Label)) + for _, lp := range m.Label { + pairs = append(pairs, lp.GetName()+"="+lp.GetValue()) + } + sort.Strings(pairs) + return strings.Join(pairs, ",") +} + +// gatherLabelSets returns the sorted label-set strings of every series published +// under the given fully-qualified metric name. +func gatherLabelSets(t *testing.T, reg *prometheus.Registry, name string) []string { + t.Helper() + mfs, err := reg.Gather() + require.NoError(t, err) + var out []string + for _, mf := range mfs { + if mf.GetName() != name { + continue + } + for _, m := range mf.Metric { + out = append(out, labelSet(m)) + } + } + sort.Strings(out) + return out +} + +// gatherSeriesValues returns label-set → value for every series under the given name. +// For counters/gauges the value comes from Counter/Gauge; for histograms it returns sample count. +func gatherSeriesValues(t *testing.T, reg *prometheus.Registry, name string) map[string]float64 { + t.Helper() + mfs, err := reg.Gather() + require.NoError(t, err) + out := map[string]float64{} + for _, mf := range mfs { + if mf.GetName() != name { + continue + } + for _, m := range mf.Metric { + switch { + case m.Counter != nil: + out[labelSet(m)] = m.Counter.GetValue() + case m.Gauge != nil: + out[labelSet(m)] = m.Gauge.GetValue() + case m.Histogram != nil: + out[labelSet(m)] = float64(m.Histogram.GetSampleCount()) + } + } + } + return out +} + +// --- Store gauge reset semantics --- + +func TestStoreMetricExporter_ResetChannels_DropsStaleLabelTuples(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + // First tick: two assets open. + exp.SetChannels("usdc", core.ChannelStatusOpen, 3) + exp.SetChannels("eth", core.ChannelStatusOpen, 1) + require.ElementsMatch(t, + []string{"asset=eth,status=open", "asset=usdc,status=open"}, + gatherLabelSets(t, reg, "nitronode_channels_total"), + ) + + // Second tick: eth last channel closes — only usdc reported. + exp.ResetChannels() + exp.SetChannels("usdc", core.ChannelStatusOpen, 2) + assert.Equal(t, + []string{"asset=usdc,status=open"}, + gatherLabelSets(t, reg, "nitronode_channels_total"), + "stale eth tuple must drop after Reset+republish", + ) +} + +func TestStoreMetricExporter_ResetAppSessions_DropsStaleLabelTuples(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + exp.SetAppSessions("app-a", app.AppSessionStatusOpen, 2) + exp.SetAppSessions("app-b", app.AppSessionStatusOpen, 1) + + exp.ResetAppSessions() + exp.SetAppSessions("app-a", app.AppSessionStatusOpen, 4) + + assert.Equal(t, + []string{"application_id=app-a,status=open"}, + gatherLabelSets(t, reg, "nitronode_app_sessions_total"), + ) +} + +func TestStoreMetricExporter_ResetTotalValueLocked_DropsStaleLabelTuples(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + exp.SetTotalValueLocked("dom", "usdc", 100) + exp.SetTotalValueLocked("dom", "eth", 5) + + exp.ResetTotalValueLocked() + exp.SetTotalValueLocked("dom", "usdc", 80) + + assert.Equal(t, + []string{"asset=usdc,domain=dom"}, + gatherLabelSets(t, reg, "nitronode_total_value_locked"), + ) +} + +func TestStoreMetricExporter_ResetNodeBalance_DropsStaleLabelTuples(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + exp.SetNodeBalance("1", "usdc", 100) + exp.SetNodeBalance("137", "matic", 50) + + exp.ResetNodeBalance() + exp.SetNodeBalance("1", "usdc", 90) + + assert.Equal(t, + []string{"asset=usdc,blockchain_id=1"}, + gatherLabelSets(t, reg, "nitronode_node_balance"), + ) +} + +func TestStoreMetricExporter_ResetUserBalances_ClearsAllThreeGauges(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + exp.SetUserBalanceTotal("1", "usdc", 100) + exp.SetUserBalanceUnderfunded("1", "usdc", 10) + exp.SetUserBalanceReleasable("1", "usdc", 5) + exp.SetUserBalanceTotal("137", "matic", 50) + + exp.ResetUserBalances() + exp.SetUserBalanceTotal("1", "usdc", 90) + // underfunded / releasable intentionally not re-set this tick. + + assert.Equal(t, + []string{"asset=usdc,blockchain_id=1"}, + gatherLabelSets(t, reg, "nitronode_user_balance_total"), + ) + assert.Empty(t, + gatherLabelSets(t, reg, "nitronode_user_balance_underfunded"), + "underfunded must be cleared with the family", + ) + assert.Empty(t, + gatherLabelSets(t, reg, "nitronode_user_balance_releasable"), + "releasable must be cleared with the family", + ) +} + +// ResetActiveUsers / ResetActiveAppSessions use DeletePartialMatch(timespan=...), +// so a failure in one timespan must not blank the others. + +func TestStoreMetricExporter_ResetActiveUsers_ClearsOnlyTargetTimespan(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + exp.SetActiveUsers("usdc", "day", 10) + exp.SetActiveUsers("usdc", "week", 50) + exp.SetActiveUsers("usdc", "month", 200) + exp.SetActiveUsers("eth", "day", 1) + exp.SetActiveUsers("eth", "week", 4) + + exp.ResetActiveUsers("day") + + got := gatherLabelSets(t, reg, "nitronode_users_active") + assert.NotContains(t, got, "asset=usdc,timespan=day") + assert.NotContains(t, got, "asset=eth,timespan=day") + assert.Contains(t, got, "asset=usdc,timespan=week") + assert.Contains(t, got, "asset=usdc,timespan=month") + assert.Contains(t, got, "asset=eth,timespan=week") +} + +func TestStoreMetricExporter_ResetActiveAppSessions_ClearsOnlyTargetTimespan(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewStoreMetricExporter(reg) + require.NoError(t, err) + + exp.SetActiveAppSessions("app-a", "day", 3) + exp.SetActiveAppSessions("app-a", "week", 10) + exp.SetActiveAppSessions("app-b", "day", 1) + exp.SetActiveAppSessions("app-b", "month", 7) + + exp.ResetActiveAppSessions("day") + + got := gatherLabelSets(t, reg, "nitronode_app_sessions_active") + assert.NotContains(t, got, "application_id=app-a,timespan=day") + assert.NotContains(t, got, "application_id=app-b,timespan=day") + assert.Contains(t, got, "application_id=app-a,timespan=week") + assert.Contains(t, got, "application_id=app-b,timespan=month") +} + +// --- Runtime exporter cold-start seeding --- + +func TestNewRuntimeMetricExporter_SeedsChannelStateSigValidations(t *testing.T) { + reg := prometheus.NewRegistry() + _, err := NewRuntimeMetricExporter(reg) + require.NoError(t, err) + + got := gatherSeriesValues(t, reg, "nitronode_channel_state_sig_validations_total") + // One series per (signer_type × result) pair, all at value 0. + expected := map[string]float64{} + for _, st := range core.ChannelSignerTypes { + for _, res := range allActionResults { + expected["result="+res.String()+",sig_type="+st.String()] = 0 + } + } + assert.Equal(t, expected, got) +} + +func TestSeedRPCMethodMetrics_PublishesZeroValuedSeries(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewRuntimeMetricExporter(reg) + require.NoError(t, err) + + rt := exp.(*runtimeMetricExporter) + methods := []string{"channels.v1.submit_state", "app_sessions.v1.create_app_session"} + rt.SeedRPCMethodMetrics(methods, nil) + + // rpc_messages_emitted_total: 3 msg_types × N methods, all at 0. + msgs := gatherSeriesValues(t, reg, "nitronode_rpc_messages_emitted_total") + require.Len(t, msgs, 3*len(methods)) + for _, m := range methods { + for _, mt := range []string{"req", "resp", "error"} { + key := "method=" + m + ",msg_type=" + mt + v, ok := msgs[key] + assert.True(t, ok, "missing series %s", key) + assert.Zero(t, v) + } + } + + // rpc_requests_total: N methods × {path=default} × 2 results, all at 0. + reqs := gatherSeriesValues(t, reg, "nitronode_rpc_requests_total") + require.Len(t, reqs, 2*len(methods)) + for _, m := range methods { + for _, res := range allActionResults { + key := "method=" + m + ",path=default,result=" + res.String() + v, ok := reqs[key] + assert.True(t, ok, "missing series %s", key) + assert.Zero(t, v) + } + } + + // rpc_request_duration_seconds (histogram): same label space, sample count 0. + durs := gatherSeriesValues(t, reg, "nitronode_rpc_request_duration_seconds") + assert.Len(t, durs, 2*len(methods)) + for _, v := range durs { + assert.Zero(t, v) + } + + // rpc_inflight: one series per method (bounded `method` label), gauge at 0. + inflight := gatherSeriesValues(t, reg, "nitronode_rpc_inflight") + require.Len(t, inflight, len(methods)) + for _, m := range methods { + v, ok := inflight["method="+m] + assert.True(t, ok, "missing rpc_inflight series for method %s", m) + assert.Zero(t, v) + } +} + +func TestSeedRPCMethodMetrics_EmptyMethodsIsNoop(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewRuntimeMetricExporter(reg) + require.NoError(t, err) + + rt := exp.(*runtimeMetricExporter) + rt.SeedRPCMethodMetrics(nil, nil) + + assert.Empty(t, gatherSeriesValues(t, reg, "nitronode_rpc_messages_emitted_total")) + assert.Empty(t, gatherSeriesValues(t, reg, "nitronode_rpc_requests_total")) + assert.Empty(t, gatherSeriesValues(t, reg, "nitronode_rpc_inflight")) +} + +func TestSeedRPCMethodMetrics_EnumeratesBoundedPaths(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewRuntimeMetricExporter(reg) + require.NoError(t, err) + + rt := exp.(*runtimeMetricExporter) + const boundedMethod = "channels.v1.submit_state" + const defaultMethod = "app_sessions.v1.create_app_session" + paths := []string{"escrow_lock", "void", "commit"} + methods := []string{boundedMethod, defaultMethod} + + rt.SeedRPCMethodMetrics(methods, map[string][]string{boundedMethod: paths}) + + reqs := gatherSeriesValues(t, reg, "nitronode_rpc_requests_total") + // boundedMethod: len(paths) × len(allActionResults). defaultMethod: 1 × len(allActionResults). + require.Len(t, reqs, (len(paths)+1)*len(allActionResults)) + + // Every (boundedMethod, path, result) tuple from the map appears. + for _, p := range paths { + for _, res := range allActionResults { + key := "method=" + boundedMethod + ",path=" + p + ",result=" + res.String() + _, ok := reqs[key] + assert.True(t, ok, "missing series %s", key) + } + } + + // boundedMethod has no path="default" series — caller declared the full domain. + for _, res := range allActionResults { + key := "method=" + boundedMethod + ",path=default,result=" + res.String() + _, ok := reqs[key] + assert.False(t, ok, "unexpected default-path series for bounded method: %s", key) + } + + // Method absent from the map falls back to path="default". + for _, res := range allActionResults { + key := "method=" + defaultMethod + ",path=default,result=" + res.String() + _, ok := reqs[key] + assert.True(t, ok, "missing default-path series %s", key) + } +} + +func TestSeedBlockchainEventMetrics_PublishesZeroValuedSeries(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewRuntimeMetricExporter(reg) + require.NoError(t, err) + + rt := exp.(*runtimeMetricExporter) + ids := []uint64{1, 137, 8453} + rt.SeedBlockchainEventMetrics(ids) + + got := gatherSeriesValues(t, reg, "nitronode_blockchain_events_total") + require.Len(t, got, len(ids)*len(allActionResults)) + for _, id := range ids { + for _, res := range allActionResults { + key := "blockchain_id=" + strconv.FormatUint(id, 10) + ",result=" + res.String() + v, ok := got[key] + assert.True(t, ok, "missing series %s", key) + assert.Zero(t, v) + } + } +} + +func TestSeedBlockchainEventMetrics_PreservesPriorIncrements(t *testing.T) { + reg := prometheus.NewRegistry() + exp, err := NewRuntimeMetricExporter(reg) + require.NoError(t, err) + + exp.IncBlockchainEvent(1, true) + exp.IncBlockchainEvent(1, true) + + rt := exp.(*runtimeMetricExporter) + rt.SeedBlockchainEventMetrics([]uint64{1, 137}) + + got := gatherSeriesValues(t, reg, "nitronode_blockchain_events_total") + assert.Equal(t, 2.0, got["blockchain_id=1,result=success"], + "WithLabelValues without Inc must not reset existing counter") + assert.Equal(t, 0.0, got["blockchain_id=1,result=failed"]) + assert.Equal(t, 0.0, got["blockchain_id=137,result=success"]) + assert.Equal(t, 0.0, got["blockchain_id=137,result=failed"]) +} diff --git a/nitronode/metrics/interface.go b/nitronode/metrics/interface.go new file mode 100644 index 000000000..ccb2b9f64 --- /dev/null +++ b/nitronode/metrics/interface.go @@ -0,0 +1,116 @@ +package metrics + +import ( + "time" + + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +// defaultApplicationIDLabelValue is the value used for the application_id +// metric label when a request arrives without an app_id query parameter, so +// dashboards have a readable bucket for unattributed traffic. +const defaultApplicationIDLabelValue = "_DEFAULT" + +// getApplicationIDLabelValue returns applicationID unchanged if non-empty, +// otherwise defaultApplicationIDLabelValue. +func getApplicationIDLabelValue(applicationID string) string { + if applicationID == "" { + return defaultApplicationIDLabelValue + } + return applicationID +} + +// RuntimeMetricExporter defines the interface for recording runtime metrics across various components of the system. +type RuntimeMetricExporter interface { + // Shared + IncUserState(asset string, homeBlockchainID uint64, transition core.TransitionType, applicationID string) + RecordTransaction(asset string, txType core.TransactionType, amount decimal.Decimal, applicationID string) + IncChannelStateSigValidation(sigType core.ChannelSignerType, success bool) + IncChannelSessionKeys() + IncAppSessionKeys() + + // api/rpc_router + IncRPCMessage(msgType rpc.MsgType, method string) + IncRPCRequest(method, path string, success bool) + ObserveRPCDuration(method, path string, success bool, duration time.Duration) + SetRPCConnections(applicationID string, count uint32) + IncRPCInflight(method string) + DecRPCInflight(method string) + + // api/app_session_v1 + IncAppStateUpdate(applicationID string) + IncAppSessionUpdateSigValidation(applicationID string, sigType app.AppSessionSignerTypeV1, success bool) + + // Blockchain Worker + IncBlockchainAction(asset string, blockchainID uint64, actionType string, success bool) + + // Event Listener + IncBlockchainEvent(blockchainID uint64, handledSuccessfully bool) + + // store/database (instrumented via gorm callbacks; see metrics.RegisterDBCallbacks) + ObserveDBQueryDuration(queryKind string, duration time.Duration) + + // Seeders publish 0-valued series for label tuples whose full domain is known at + // startup, so PromQL queries (and absent()-style alerts) return defined values + // during the cold-start window before any traffic or chain event has arrived. + SeedRPCMethodMetrics(methods []string, methodPaths map[string][]string) + SeedBlockchainEventMetrics(blockchainIDs []uint64) +} + +// noopRuntimeMetricExporter is a no-op implementation for use in tests. +type noopRuntimeMetricExporter struct{} + +func NewNoopRuntimeMetricExporter() RuntimeMetricExporter { return noopRuntimeMetricExporter{} } +func (noopRuntimeMetricExporter) IncUserState(string, uint64, core.TransitionType, string) {} +func (noopRuntimeMetricExporter) RecordTransaction(string, core.TransactionType, decimal.Decimal, string) { +} +func (noopRuntimeMetricExporter) IncChannelStateSigValidation(core.ChannelSignerType, bool) {} +func (noopRuntimeMetricExporter) IncChannelSessionKeys() {} +func (noopRuntimeMetricExporter) IncAppSessionKeys() {} +func (noopRuntimeMetricExporter) IncRPCMessage(rpc.MsgType, string) {} +func (noopRuntimeMetricExporter) IncRPCRequest(string, string, bool) {} +func (noopRuntimeMetricExporter) ObserveRPCDuration(string, string, bool, time.Duration) {} +func (noopRuntimeMetricExporter) SetRPCConnections(string, uint32) {} +func (noopRuntimeMetricExporter) IncAppStateUpdate(string) {} +func (noopRuntimeMetricExporter) IncAppSessionUpdateSigValidation(string, app.AppSessionSignerTypeV1, bool) { +} +func (noopRuntimeMetricExporter) IncBlockchainAction(string, uint64, string, bool) { +} +func (noopRuntimeMetricExporter) IncBlockchainEvent(uint64, bool) {} +func (noopRuntimeMetricExporter) IncRPCInflight(string) {} +func (noopRuntimeMetricExporter) DecRPCInflight(string) {} +func (noopRuntimeMetricExporter) ObserveDBQueryDuration(string, time.Duration) {} +func (noopRuntimeMetricExporter) SeedRPCMethodMetrics([]string, map[string][]string) {} +func (noopRuntimeMetricExporter) SeedBlockchainEventMetrics([]uint64) {} + +// StoreMetricExporter defines the interface for setting metrics that are stored and updated by a separate metric worker. +// +// The Reset* methods drop every series in their respective gauges so the next batch of +// Set* calls re-publishes a complete picture. Without this, label tuples that disappear +// from the store (e.g., the last open channel for an asset closes) would keep their +// previous non-zero value indefinitely because the underlying GROUP BY queries omit +// zero-count buckets. Callers should only reset after a successful query — resetting +// on error would blank a healthy gauge during a transient DB outage. +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) + SetNodeBalance(blockchainID, asset string, value float64) + SetUserBalanceTotal(blockchainID, asset string, value float64) + SetUserBalanceUnderfunded(blockchainID, asset string, value float64) + SetUserBalanceReleasable(blockchainID, asset string, value float64) + + ResetAppSessions() + ResetChannels() + ResetTotalValueLocked() + ResetNodeBalance() + ResetUserBalances() + ResetActiveUsers(timeSpanLabel string) + ResetActiveAppSessions(timeSpanLabel string) +} diff --git a/nitronode/runtime.go b/nitronode/runtime.go new file mode 100644 index 000000000..3cdd7f3e8 --- /dev/null +++ b/nitronode/runtime.go @@ -0,0 +1,515 @@ +package main + +import ( + "context" + "embed" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ilyakaznacheev/cleanenv" + "github.com/joho/godotenv" + "github.com/prometheus/client_golang/prometheus" + + "github.com/layer-3/nitrolite/nitronode/action_gateway" + "github.com/layer-3/nitrolite/nitronode/metrics" + "github.com/layer-3/nitrolite/nitronode/store/database" + "github.com/layer-3/nitrolite/nitronode/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 +var embedMigrations embed.FS + +var Version = "v1.0.0" // set at build time with -ldflags "-X main.Version=x.y.z" + +type Backbone struct { + NodeVersion string + ChannelMinChallengeDuration uint32 + ChannelMaxChallengeDuration uint32 + AppRegistryEnabled bool + BlockchainRPCs map[uint64]string + BlockchainGasLimit uint64 + ValidationLimits ValidationLimits + RateLimitPerSec float64 + RateLimitBurst float64 + + DbStore database.DatabaseStore + MemoryStore memory.MemoryStore + ActionGateway action_gateway.ActionAllower + RpcNode rpc.Node + StateSigner sign.Signer + TxSigner sign.Signer + Logger log.Logger + RuntimeMetrics metrics.RuntimeMetricExporter + StoreMetrics metrics.StoreMetricExporter + closers []func() error +} + +// Close releases resources held by the backbone (e.g., KMS client connections). +func (b *Backbone) Close() error { + var firstErr error + for _, fn := range b.closers { + if err := fn(); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + +type FullConfig struct { + Database database.DatabaseConfig + ChannelMinChallengeDuration uint32 `yaml:"channel_min_challenge_duration" env:"NITRONODE_CHANNEL_MIN_CHALLENGE_DURATION" env-default:"86400"` // 24 hours + ChannelMaxChallengeDuration uint32 `yaml:"channel_max_challenge_duration" env:"NITRONODE_CHANNEL_MAX_CHALLENGE_DURATION" env-default:"604800"` // 7 days + ActionLimitsEnabled bool `yaml:"action_limits_enabled" env:"NITRONODE_ACTION_LIMITS_ENABLED"` + AppRegistryEnabled bool `yaml:"app_registry_enabled" env:"NITRONODE_APP_REGISTRY_ENABLED"` + Signer SignerConfig `yaml:"signer"` + SignerType string `yaml:"signer_type" env:"NITRONODE_SIGNER_TYPE" env-default:"key"` // "key" or "gcp-kms" + SignerKey string `yaml:"signer_key" env:"NITRONODE_SIGNER_KEY"` // required when signer_type=key + GCPKMSKeyName string `yaml:"gcp_kms_key_name" env:"NITRONODE_GCP_KMS_KEY_NAME"` // required when signer_type=gcp-kms + ValidationLimits ValidationLimits `yaml:"validation_limits"` + RateLimitPerSec float64 `yaml:"rate_limit_per_sec" env:"NITRONODE_RATE_LIMIT_PER_SEC" env-default:"10"` + RateLimitBurst float64 `yaml:"rate_limit_burst" env:"NITRONODE_RATE_LIMIT_BURST" env-default:"20"` + WsProcessBufferSize int `yaml:"ws_process_buffer_size" env:"NITRONODE_WS_PROCESS_BUFFER_SIZE" env-default:"64"` + WsWriteBufferSize int `yaml:"ws_write_buffer_size" env:"NITRONODE_WS_WRITE_BUFFER_SIZE" env-default:"64"` + // WsMaxMessageSize caps inbound WebSocket frame size in bytes. Frames over the + // cap close the connection with WebSocket close code 1009 before allocation. + // 128 KiB fits any legitimate v1 RPC with substantial headroom. + WsMaxMessageSize int64 `yaml:"ws_max_message_size" env:"NITRONODE_WS_MAX_MESSAGE_SIZE" env-default:"131072"` + // WsBytesPerSec is the steady-state byte budget per connection. Set <0 to disable. + WsBytesPerSec float64 `yaml:"ws_bytes_per_sec" env:"NITRONODE_WS_BYTES_PER_SEC" env-default:"262144"` + // WsBytesBurst is the burst capacity of the per-connection byte bucket. + WsBytesBurst float64 `yaml:"ws_bytes_burst" env:"NITRONODE_WS_BYTES_BURST" env-default:"1048576"` + // BlockchainGasLimit forces a fixed GasLimit on every blockchain transaction, + // bypassing eth_estimateGas. 0 = use estimate (default). Set when an RPC + // rejects estimateGas — e.g. XRPL EVM testnet ("gas cap cannot be lower than 21000"). + BlockchainGasLimit uint64 `yaml:"blockchain_gas_limit" env:"NITRONODE_BLOCKCHAIN_GAS_LIMIT" env-default:"0"` +} + +type SignerConfig struct { + Type string `yaml:"type" env:"NITRONODE_SIGNER_TYPE" env-default:"key"` // "key" or "gcp-kms" + Key string `yaml:"key" env:"NITRONODE_SIGNER_KEY"` // required when type=key + GCPKMSKeyName string `yaml:"gcp_kms_key_name" env:"NITRONODE_GCP_KMS_KEY_NAME"` // required when type=gcp-kms +} + +// ValidationLimits defines configurable upper bounds for dynamic-length request fields. +type ValidationLimits struct { + MaxParticipants int `yaml:"max_participants" env:"NITRONODE_MAX_PARTICIPANTS" env-default:"32"` + MaxSessionDataLen int `yaml:"max_session_data_len" env:"NITRONODE_MAX_SESSION_DATA_LEN" env-default:"1024"` + MaxAppMetadataLen int `yaml:"max_app_metadata_len" env:"NITRONODE_MAX_APP_METADATA_LEN" env-default:"1024"` + MaxSessionKeyIDs int `yaml:"max_session_key_ids" env:"NITRONODE_MAX_SESSION_KEY_IDS" env-default:"10"` + MaxSignedUpdates int `yaml:"max_signed_updates" env:"NITRONODE_MAX_SIGNED_UPDATES" env-default:"0"` + MaxSessionKeysPerUser int `yaml:"max_session_keys_per_user" env:"NITRONODE_MAX_SESSION_KEYS_PER_USER" env-default:"100"` +} + +// intrinsicTxGas is the minimum gas required for any Ethereum transaction. +const intrinsicTxGas = 21000 + +func validateBlockchainGasLimit(gasLimit uint64) error { + if gasLimit > 0 && gasLimit < intrinsicTxGas { + return fmt.Errorf( + "NITRONODE_BLOCKCHAIN_GAS_LIMIT must be 0 (auto-estimate) or >= %d, got %d", + intrinsicTxGas, + gasLimit, + ) + } + return nil +} + +func validateChannelChallengeConfig(minChallenge, maxChallenge uint32) error { + if minChallenge < core.ChannelMinChallengeDuration { + return fmt.Errorf( + "NITRONODE_CHANNEL_MIN_CHALLENGE_DURATION must be at least %d seconds, got %d", + core.ChannelMinChallengeDuration, + minChallenge, + ) + } + if maxChallenge > core.ChannelMaxChallengeDuration { + return fmt.Errorf( + "NITRONODE_CHANNEL_MAX_CHALLENGE_DURATION must be at most %d seconds, got %d", + core.ChannelMaxChallengeDuration, + maxChallenge, + ) + } + if minChallenge > maxChallenge { + return fmt.Errorf( + "NITRONODE_CHANNEL_MIN_CHALLENGE_DURATION must be <= NITRONODE_CHANNEL_MAX_CHALLENGE_DURATION, got min=%d max=%d", + minChallenge, + maxChallenge, + ) + } + return nil +} + +// InitBackbone initializes the backbone components of the application. +func InitBackbone() *Backbone { + closers := []func() error{} // collect closer functions for resources that need cleanup + + // ------------------------------------------------ + // Logger + // ------------------------------------------------ + + logger := initLogger() + + // ------------------------------------------------ + // (Preparation) + // ------------------------------------------------ + + configDirPath := initBase(logger) + + var conf FullConfig + if err := cleanenv.ReadEnv(&conf); err != nil { + logger.Fatal("failed to read env", "err", err) + } + if err := validateChannelChallengeConfig(conf.ChannelMinChallengeDuration, conf.ChannelMaxChallengeDuration); err != nil { + logger.Fatal("invalid channel challenge duration config", "error", err) + } + if err := validateBlockchainGasLimit(conf.BlockchainGasLimit); err != nil { + logger.Fatal("invalid blockchain gas limit config", "error", err) + } + + logger.Info("config loaded", "version", Version) + + // ------------------------------------------------ + // Database Store + // ------------------------------------------------ + + db, err := database.ConnectToDB(conf.Database, embedMigrations) + if err != nil { + logger.Fatal("failed to load database store", "error", err) + } + dbStore := database.NewDBStore(db) + + // ------------------------------------------------ + // Memory Store + // ------------------------------------------------ + + memoryStore, err := memory.NewMemoryStoreV1FromConfig(configDirPath) + if err != nil { + logger.Fatal("failed to load blockchains", "error", err) + } + + // ------------------------------------------------ + // Action Gateway + // ------------------------------------------------ + + var actionGateway action_gateway.ActionAllower + if conf.ActionLimitsEnabled { + actionGateway, err = action_gateway.NewActionGatewayFromYaml(configDirPath) + if err != nil { + logger.Fatal("failed to initialize action gateway", "error", err) + } + } else { + actionGateway = action_gateway.NewPermissiveActionAllower() + logger.Info("action limits disabled, using permissive action allower") + } + + // ------------------------------------------------ + // Signer + // ------------------------------------------------ + + txSigner, closer := initSigner(logger, conf.Signer.Type, conf.Signer.Key, conf.Signer.GCPKMSKeyName) + closers = append(closers, closer) + + stateSigner, err := sign.NewEthereumMsgSignerFromRaw(txSigner) + if err != nil { + logger.Fatal("failed to wrap tx signer as state signer", "error", err) + } + + logger.Info("signer initialized", "type", conf.Signer.Type, "address", stateSigner.PublicKey().Address()) + + // ------------------------------------------------ + // Metrics + // ------------------------------------------------ + + runtimeMetrics, err := metrics.NewRuntimeMetricExporter(prometheus.DefaultRegisterer) + if err != nil { + logger.Fatal("failed to initialize runtime metric exporter", "error", err) + } + storeMetrics, err := metrics.NewStoreMetricExporter(prometheus.DefaultRegisterer) + if err != nil { + logger.Fatal("failed to initialize store metric exporter", "error", err) + } + + // Wire DB instrumentation now that both gorm.DB and the runtime exporter + // exist: per-query histograms via gorm callbacks, plus the standard + // go_sql_* pool-stats collector. + if err := database.RegisterMetricsCallbacks(db, runtimeMetrics); err != nil { + logger.Fatal("failed to register database metric callbacks", "error", err) + } + if err := database.RegisterDBStatsCollector(db, prometheus.DefaultRegisterer); err != nil { + logger.Fatal("failed to register database stats collector", "error", err) + } + + // ------------------------------------------------ + // RPC Node + // ------------------------------------------------ + + // MF-C01 requires a hard frame-size cap; refuse to start without one even if + // the operator zeroed the env. The library would substitute its own default, + // but we want the misconfiguration surfaced rather than papered over. + if conf.WsMaxMessageSize <= 0 { + logger.Fatal( + "NITRONODE_WS_MAX_MESSAGE_SIZE must be > 0; the WebSocket frame cap cannot be disabled", + "ws_max_message_size", conf.WsMaxMessageSize, + ) + } + + bytesPerSec := conf.WsBytesPerSec + bytesBurst := conf.WsBytesBurst + // When the per-connection byte budget is enabled, the burst must be at least + // the max message size; otherwise every legitimate frame that passes + // SetReadLimit would still be rejected by the bucket on arrival, taking the + // node out via config alone. Fail fast at startup. + if bytesPerSec > 0 { + if bytesBurst <= 0 { + logger.Fatal( + "NITRONODE_WS_BYTES_BURST must be > 0 when NITRONODE_WS_BYTES_PER_SEC is enabled", + "ws_bytes_burst", bytesBurst, + "ws_bytes_per_sec", bytesPerSec, + ) + } + if bytesBurst < float64(conf.WsMaxMessageSize) { + logger.Fatal( + "NITRONODE_WS_BYTES_BURST must be >= NITRONODE_WS_MAX_MESSAGE_SIZE when byte limiting is enabled", + "ws_bytes_burst", bytesBurst, + "ws_max_message_size", conf.WsMaxMessageSize, + ) + } + } + rpcNode, err := rpc.NewWebsocketNode(rpc.WebsocketNodeConfig{ + Logger: logger, + ObserveConnections: runtimeMetrics.SetRPCConnections, + WsConnProcessBufferSize: conf.WsProcessBufferSize, + WsConnWriteBufferSize: conf.WsWriteBufferSize, + WsConnMaxMessageSize: conf.WsMaxMessageSize, + NewFrameRateLimiter: func() rpc.FrameRateLimiter { + if bytesPerSec <= 0 { + return rpc.NoopFrameRateLimiter{} + } + return rpc.NewByteTokenBucket(bytesPerSec, bytesBurst) + }, + }) + if err != nil { + logger.Fatal("failed to initialize RPC node", "error", err) + } + + // ------------------------------------------------ + // Blockchain RPCs + // ------------------------------------------------ + + blockchainRPCs := initBlockchainRPCs(logger, memoryStore) + + // Seed per-chain event counters at 0 so a chain whose listener has not yet + // emitted an event still appears in /metrics (lets stalled-listener alerts + // based on absent()/rate() evaluate against defined series). + blockchainIDs := make([]uint64, 0, len(blockchainRPCs)) + for id := range blockchainRPCs { + blockchainIDs = append(blockchainIDs, id) + } + runtimeMetrics.SeedBlockchainEventMetrics(blockchainIDs) + + return &Backbone{ + NodeVersion: Version, + ChannelMinChallengeDuration: conf.ChannelMinChallengeDuration, + ChannelMaxChallengeDuration: conf.ChannelMaxChallengeDuration, + AppRegistryEnabled: conf.AppRegistryEnabled, + BlockchainRPCs: blockchainRPCs, + BlockchainGasLimit: conf.BlockchainGasLimit, + ValidationLimits: conf.ValidationLimits, + RateLimitPerSec: conf.RateLimitPerSec, + RateLimitBurst: conf.RateLimitBurst, + + DbStore: dbStore, + MemoryStore: memoryStore, + ActionGateway: actionGateway, + RpcNode: rpcNode, + StateSigner: stateSigner, + TxSigner: txSigner, + Logger: logger, + RuntimeMetrics: runtimeMetrics, + StoreMetrics: storeMetrics, + closers: closers, + } +} + +func initBase(logger log.Logger) string { + migrateLegacyClearnodeEnv(logger) + + configDirPath := os.Getenv("NITRONODE_CONFIG_DIR_PATH") + if configDirPath == "" { + configDirPath = "." + } + + configDotEnvPath := filepath.Join(configDirPath, ".env") + logger.Info("loading .env file", "path", configDotEnvPath) + if err := godotenv.Load(configDotEnvPath); err != nil { + logger.Warn(".env file not found") + } + + return configDirPath +} + +// migrateLegacyClearnodeEnv copies any CLEARNODE_* environment variables to their +// NITRONODE_* counterparts when the new name is unset. The legacy prefix is +// deprecated and will be removed in a future release. +func migrateLegacyClearnodeEnv(logger log.Logger) { + const legacyPrefix = "CLEARNODE_" + const newPrefix = "NITRONODE_" + for _, kv := range os.Environ() { + eq := strings.IndexByte(kv, '=') + if eq < 0 { + continue + } + key := kv[:eq] + if !strings.HasPrefix(key, legacyPrefix) { + continue + } + newKey := newPrefix + strings.TrimPrefix(key, legacyPrefix) + if _, set := os.LookupEnv(newKey); set { + continue + } + if err := os.Setenv(newKey, kv[eq+1:]); err != nil { + logger.Warn("failed to migrate legacy env var", "from", key, "to", newKey, "error", err) + continue + } + logger.Warn("CLEARNODE_* env var is deprecated, use NITRONODE_*", "from", key, "to", newKey) + } +} + +func initLogger() log.Logger { + var loggerConf log.Config + if err := cleanenv.ReadEnv(&loggerConf); err != nil { + panic("failed to read logger config from env: " + err.Error()) + } + return log.NewZapLogger(loggerConf).WithName("main") +} + +func initSigner(logger log.Logger, signerType, privateKey, gcpKMSKeyName string) (sign.Signer, func() error) { + var signer sign.Signer + var err error + + closer := func() error { return nil } // default no-op closer + + switch signerType { + case "key": + if privateKey == "" { + logger.Fatal("NITRONODE_SIGNER_KEY is required when NITRONODE_SIGNER_TYPE=key") + } + signer, err = sign.NewEthereumRawSigner(privateKey) + if err != nil { + logger.Fatal("failed to initialise tx signer", "error", err) + } + case "gcp-kms": + if gcpKMSKeyName == "" { + logger.Fatal("NITRONODE_GCP_KMS_KEY_NAME is required when NITRONODE_SIGNER_TYPE=gcp-kms") + } + kmsCtx, kmsCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer kmsCancel() + kmsSigner, kmsErr := gcp.NewSigner(kmsCtx, gcpKMSKeyName) + if kmsErr != nil { + logger.Fatal("failed to initialise GCP KMS signer", "error", kmsErr) + } + closer = kmsSigner.Close + signer = kmsSigner + default: + logger.Fatal("unsupported NITRONODE_SIGNER_TYPE", "type", signerType) + } + + return signer, closer +} + +func initBlockchainRPCs(logger log.Logger, memoryStore memory.MemoryStore) map[uint64]string { + blockchains, err := memoryStore.GetBlockchains() + if err != nil { + logger.Fatal("failed to get blockchains", "error", err) + } + + blockchainRPCs := make(map[uint64]string) + for _, bc := range blockchains { + envVarName := "NITRONODE_BLOCKCHAIN_RPC_" + strings.ToUpper(bc.Name) + rpcURL := os.Getenv(envVarName) + if rpcURL == "" { + logger.Fatal("blockchain RPC URL not set in env", "blockchainID", bc.ID, "env_var", envVarName) + } + + // Test connection + if err := checkChainId(rpcURL, bc.ID); err != nil { + logger.Fatal("failed to verify blockchain RPC", "blockchainID", bc.ID, "error", err) + } + + // Verify ChannelHub version + channelHubAddress := common.HexToAddress(bc.ChannelHubAddress) + if err := checkChannelHubVersion(rpcURL, channelHubAddress, core.ChannelHubVersion); err != nil { + logger.Fatal("failed to verify ChannelHub version", "blockchainID", bc.ID, "address", bc.ChannelHubAddress, "error", err) + } + + blockchainRPCs[bc.ID] = rpcURL + } + + return blockchainRPCs +} + +// checkChainId connects to an RPC endpoint and verifies it returns the expected chain ID. +// This ensures the RPC URL points to the correct blockchain network. +// The function uses a 5-second timeout for the connection and chain ID query. +func checkChainId(blockchainRPC string, expectedChainID uint64) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client, err := ethclient.DialContext(ctx, blockchainRPC) + if err != nil { + return fmt.Errorf("failed to connect to blockchain RPC: %w", err) + } + defer client.Close() + + chainID, err := client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID from blockchain RPC: %w", err) + } + + if chainID.Uint64() != expectedChainID { + return fmt.Errorf("unexpected chain ID from blockchain RPC: got %d, want %d", chainID.Uint64(), expectedChainID) + } + + return nil +} + +// checkChannelHubVersion verifies that the ChannelHub contract at the given address +// has the expected VERSION constant value. +// The function uses a 5-second timeout for the connection and contract calls. +func checkChannelHubVersion(blockchainRPC string, channelHubAddress common.Address, expectedVersion uint8) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client, err := ethclient.DialContext(ctx, blockchainRPC) + if err != nil { + return fmt.Errorf("failed to connect to blockchain RPC: %w", err) + } + defer client.Close() + + channelHub, err := evm.NewChannelHubCaller(channelHubAddress, client) + if err != nil { + return fmt.Errorf("failed to create ChannelHub caller: %w", err) + } + + fetchedVersion, err := channelHub.VERSION(nil) + if err != nil { + return fmt.Errorf("failed to get ChannelHub version: %w", err) + } + + if fetchedVersion != expectedVersion { + return fmt.Errorf("configured and fetched ChannelHub version mismatch: got %d, want %d", fetchedVersion, expectedVersion) + } + + return nil +} diff --git a/nitronode/runtime_config_test.go b/nitronode/runtime_config_test.go new file mode 100644 index 000000000..e77148e34 --- /dev/null +++ b/nitronode/runtime_config_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "strings" + "testing" + + "github.com/layer-3/nitrolite/pkg/core" +) + +func TestValidateChannelChallengeConfig(t *testing.T) { + tests := []struct { + name string + minChallenge uint32 + maxChallenge uint32 + wantErr bool + errorContains string + }{ + { + name: "default range passes", + minChallenge: core.ChannelMinChallengeDuration, + maxChallenge: core.ChannelMaxChallengeDuration, + }, + { + name: "stricter range passes", + minChallenge: 172800, + maxChallenge: 345600, + }, + { + name: "max above contract limit fails", + minChallenge: core.ChannelMinChallengeDuration, + maxChallenge: core.ChannelMaxChallengeDuration + 1, + wantErr: true, + errorContains: "NITRONODE_CHANNEL_MAX_CHALLENGE_DURATION", + }, + { + name: "min below contract limit fails", + minChallenge: core.ChannelMinChallengeDuration - 1, + maxChallenge: core.ChannelMaxChallengeDuration, + wantErr: true, + errorContains: "NITRONODE_CHANNEL_MIN_CHALLENGE_DURATION", + }, + { + name: "min greater than max fails", + minChallenge: core.ChannelMaxChallengeDuration, + maxChallenge: core.ChannelMinChallengeDuration, + wantErr: true, + errorContains: "must be <=", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateChannelChallengeConfig(tt.minChallenge, tt.maxChallenge) + + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.errorContains) { + t.Fatalf("expected error containing %q, got %q", tt.errorContains, err.Error()) + } + return + } + + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + }) + } +} diff --git a/clearnode/runtime_test.go b/nitronode/runtime_test.go similarity index 82% rename from clearnode/runtime_test.go rename to nitronode/runtime_test.go index 2b66d9917..e4ccdc121 100644 --- a/clearnode/runtime_test.go +++ b/nitronode/runtime_test.go @@ -9,6 +9,27 @@ import ( "github.com/layer-3/nitrolite/pkg/core" ) +func TestValidateBlockchainGasLimit(t *testing.T) { + tests := []struct { + name string + gasLimit uint64 + wantErr bool + }{ + {"zero auto-estimate", 0, false}, + {"below intrinsic", 20999, true}, + {"exactly intrinsic", 21000, false}, + {"typical block limit", 30_000_000, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateBlockchainGasLimit(tt.gasLimit) + if (err != nil) != tt.wantErr { + t.Fatalf("validateBlockchainGasLimit(%d) error = %v, wantErr %v", tt.gasLimit, err, tt.wantErr) + } + }) + } +} + func TestCheckChannelHubVersion_Manual(t *testing.T) { t.Skip("Manual test - uncomment test cases and set values before running") diff --git a/clearnode/store/database/action_log.go b/nitronode/store/database/action_log.go similarity index 100% rename from clearnode/store/database/action_log.go rename to nitronode/store/database/action_log.go diff --git a/clearnode/store/database/action_log_test.go b/nitronode/store/database/action_log_test.go similarity index 100% rename from clearnode/store/database/action_log_test.go rename to nitronode/store/database/action_log_test.go diff --git a/clearnode/store/database/app.go b/nitronode/store/database/app.go similarity index 100% rename from clearnode/store/database/app.go rename to nitronode/store/database/app.go diff --git a/clearnode/store/database/app_ledger.go b/nitronode/store/database/app_ledger.go similarity index 100% rename from clearnode/store/database/app_ledger.go rename to nitronode/store/database/app_ledger.go diff --git a/clearnode/store/database/app_ledger_test.go b/nitronode/store/database/app_ledger_test.go similarity index 100% rename from clearnode/store/database/app_ledger_test.go rename to nitronode/store/database/app_ledger_test.go diff --git a/clearnode/store/database/app_session.go b/nitronode/store/database/app_session.go similarity index 87% rename from clearnode/store/database/app_session.go rename to nitronode/store/database/app_session.go index 14c946cf4..223a79972 100644 --- a/clearnode/store/database/app_session.go +++ b/nitronode/store/database/app_session.go @@ -133,6 +133,30 @@ 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_id"` + Status app.AppSessionStatus `gorm:"column:status"` + Count uint64 `gorm:"column:count"` +} + +// GetAppSessionsCountByLabels returns current app session counts grouped by application and status. +func (s *DBStore) GetAppSessionsCountByLabels() ([]AppSessionCount, error) { + var results []AppSessionCount + err := s.db.Raw(` + SELECT application_id, + status, + COUNT(id) AS count + FROM app_sessions_v1 + GROUP BY application_id, status + `).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to get app session counts: %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/nitronode/store/database/app_session_key_state.go similarity index 58% rename from clearnode/store/database/app_session_key_state.go rename to nitronode/store/database/app_session_key_state.go index 76bcdd597..522b17754 100644 --- a/clearnode/store/database/app_session_key_state.go +++ b/nitronode/store/database/app_session_key_state.go @@ -20,6 +20,7 @@ type AppSessionKeyStateV1 struct { AppSessionIDs []AppSessionKeyAppSessionIDV1 `gorm:"foreignKey:SessionKeyStateID;references:ID"` ExpiresAt time.Time `gorm:"column:expires_at;not null"` UserSig string `gorm:"column:user_sig;not null"` + SessionKeySig string `gorm:"column:session_key_sig"` CreatedAt time.Time } @@ -58,12 +59,13 @@ func (s *DBStore) StoreAppSessionKeyState(state app.AppSessionKeyStateV1) error } dbState := AppSessionKeyStateV1{ - ID: id, - UserAddress: userAddress, - SessionKey: sessionKey, - Version: state.Version, - ExpiresAt: state.ExpiresAt.UTC(), - UserSig: state.UserSig, + ID: id, + UserAddress: userAddress, + SessionKey: sessionKey, + Version: state.Version, + ExpiresAt: state.ExpiresAt.UTC(), + UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, } if err := s.db.Create(&dbState).Error; err != nil { @@ -96,35 +98,58 @@ func (s *DBStore) StoreAppSessionKeyState(state app.AppSessionKeyStateV1) error } } + if err := upsertCurrentSessionKeyState(s.db, userAddress, sessionKey, SessionKeyKindAppSession, state.Version); err != nil { + return err + } + return nil } // GetLastAppSessionKeyStates retrieves the latest session key states for a user with optional filtering. -// Returns only the highest-version row per session key that has not expired. -func (s *DBStore) GetLastAppSessionKeyStates(wallet string, sessionKey *string) ([]app.AppSessionKeyStateV1, error) { +// Reads filter the current_session_key_states_v1 pointer table by (user_address, kind=app_session) +// and JOIN history on (user_address, session_key, version). Per-request DB work is bounded by +// the number of distinct session keys for the user, regardless of version churn in history. +// When includeInactive is false the same now is applied to both the count and the list query so +// pagination stays consistent across the two reads. Results are paginated; totalCount is the +// unpaginated total of matching session keys. +func (s *DBStore) GetLastAppSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]app.AppSessionKeyStateV1, uint32, error) { wallet = strings.ToLower(wallet) + now := time.Now().UTC() - subQuery := s.db.Model(&AppSessionKeyStateV1{}). - Select("user_address, session_key, MAX(version) as max_version"). - Where("user_address = ?", wallet). - Group("user_address, session_key") - + pointerQuery := s.db.Table("current_session_key_states_v1 AS c"). + Where("c.user_address = ? AND c.kind = ? AND c.version > 0", wallet, SessionKeyKindAppSession) if sessionKey != nil && *sessionKey != "" { - subQuery = subQuery.Where("session_key = ?", strings.ToLower(*sessionKey)) + pointerQuery = pointerQuery.Where("c.session_key = ?", strings.ToLower(*sessionKey)) + } + if !includeInactive { + pointerQuery = pointerQuery. + Joins("JOIN app_session_key_states_v1 h ON h.user_address = c.user_address AND h.session_key = c.session_key AND h.version = c.version"). + Where("h.expires_at > ?", now) } - query := s.db. - Joins("JOIN (?) AS latest ON app_session_key_states_v1.user_address = latest.user_address AND app_session_key_states_v1.session_key = latest.session_key AND app_session_key_states_v1.version = latest.max_version", subQuery). + var totalCount int64 + if err := pointerQuery.Count(&totalCount).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count session key states: %w", err) + } + + query := s.db.Model(&AppSessionKeyStateV1{}). + Joins("JOIN current_session_key_states_v1 c ON c.user_address = app_session_key_states_v1.user_address AND c.session_key = app_session_key_states_v1.session_key AND c.version = app_session_key_states_v1.version"). + Where("c.user_address = ? AND c.kind = ? AND c.version > 0", wallet, SessionKeyKindAppSession). Preload("ApplicationIDs"). Preload("AppSessionIDs"). - Order("app_session_key_states_v1.created_at DESC") + Order("app_session_key_states_v1.created_at DESC, app_session_key_states_v1.id ASC"). + Limit(int(limit)). + Offset(int(offset)) + if sessionKey != nil && *sessionKey != "" { + query = query.Where("c.session_key = ?", strings.ToLower(*sessionKey)) + } + if !includeInactive { + query = query.Where("app_session_key_states_v1.expires_at > ?", now) + } var dbStates []AppSessionKeyStateV1 if err := query.Find(&dbStates).Error; err != nil { - if err == gorm.ErrRecordNotFound { - return []app.AppSessionKeyStateV1{}, nil - } - return nil, fmt.Errorf("failed to get session key states: %w", err) + return nil, 0, fmt.Errorf("failed to get session key states: %w", err) } states := make([]app.AppSessionKeyStateV1, len(dbStates)) @@ -132,23 +157,20 @@ func (s *DBStore) GetLastAppSessionKeyStates(wallet string, sessionKey *string) states[i] = dbSessionKeyStateToCore(&dbState) } - return states, nil + return states, uint32(totalCount), nil } // GetLastAppSessionKeyVersion returns the latest version of a session key state for a user. -// Returns 0 if no state exists. +// Reads from the pointer table; returns 0 if no state exists or the pointer is at its seeded +// value (LockSessionKeyState created the row but no submit has succeeded yet). func (s *DBStore) GetLastAppSessionKeyVersion(wallet, sessionKey string) (uint64, error) { wallet = strings.ToLower(wallet) sessionKey = strings.ToLower(sessionKey) - var result struct { - Version uint64 - } - err := s.db.Model(&AppSessionKeyStateV1{}). - Select("version"). - Where("user_address = ? AND session_key = ?", wallet, sessionKey). - Order("version DESC"). - Take(&result).Error + var pointer CurrentSessionKeyStateV1 + err := s.db. + Where("user_address = ? AND session_key = ? AND kind = ?", wallet, sessionKey, SessionKeyKindAppSession). + Take(&pointer).Error if err != nil { if err == gorm.ErrRecordNotFound { @@ -157,20 +179,20 @@ func (s *DBStore) GetLastAppSessionKeyVersion(wallet, sessionKey string) (uint64 return 0, fmt.Errorf("failed to check session key state: %w", err) } - return result.Version, nil + return pointer.Version, nil } // GetLastAppSessionKeyState retrieves the latest version of a specific session key for a user. -// A newer version always supersedes older ones, even if expired. -// Returns nil if no state exists. +// A newer version always supersedes older ones, even if expired. Resolved via the pointer +// table; returns nil if no state exists. func (s *DBStore) GetLastAppSessionKeyState(wallet, sessionKey string) (*app.AppSessionKeyStateV1, error) { wallet = strings.ToLower(wallet) sessionKey = strings.ToLower(sessionKey) var dbState AppSessionKeyStateV1 err := s.db. - Where("user_address = ? AND session_key = ?", wallet, sessionKey). - Order("version DESC"). + Joins("JOIN current_session_key_states_v1 c ON c.user_address = app_session_key_states_v1.user_address AND c.session_key = app_session_key_states_v1.session_key AND c.version = app_session_key_states_v1.version AND c.kind = ?", SessionKeyKindAppSession). + Where("app_session_key_states_v1.user_address = ? AND app_session_key_states_v1.session_key = ? AND c.version > 0", wallet, sessionKey). Preload("ApplicationIDs"). Preload("AppSessionIDs"). First(&dbState).Error @@ -187,30 +209,32 @@ func (s *DBStore) GetLastAppSessionKeyState(wallet, sessionKey string) (*app.App } // GetAppSessionKeyOwner returns the user_address that owns the given session key -// authorized for the specified app session ID. Only the latest-version, non-expired key -// with matching permissions is considered. A newer version always supersedes older ones. -func (s *DBStore) GetAppSessionKeyOwner(sessionKey, appSessionId string) (string, error) { +// authorized for the specified app session ID or application ID. Only the latest-version, +// non-expired key with matching permissions is considered. A newer version always supersedes +// older ones. +// +// Both appSessionId and applicationId are accepted because the app session row may not yet +// exist in the database when this is called (notably during app session creation). Passing +// applicationId directly avoids a chicken-and-egg subquery against the not-yet-inserted row; +// callers always know the application ID either from the signed request (create) or from the +// loaded app session record (post-create state updates). +func (s *DBStore) GetAppSessionKeyOwner(sessionKey, appSessionId, applicationId string) (string, error) { sessionKey = strings.ToLower(sessionKey) appSessionId = strings.ToLower(appSessionId) - - // Subquery to get the application ID from the app session - appSubQuery := s.db.Model(&AppSessionV1{}).Select("application_id").Where("id = ?", appSessionId) - - maxVersionSubQ := s.db.Model(&AppSessionKeyStateV1{}). - Select("MAX(version)"). - Where("session_key = ?", sessionKey) + applicationId = strings.ToLower(applicationId) var dbState AppSessionKeyStateV1 err := s.db. + Joins("JOIN current_session_key_states_v1 c ON c.user_address = app_session_key_states_v1.user_address AND c.session_key = app_session_key_states_v1.session_key AND c.version = app_session_key_states_v1.version AND c.kind = ?", SessionKeyKindAppSession). Joins("LEFT JOIN app_session_key_app_sessions_v1 ON app_session_key_app_sessions_v1.session_key_state_id = app_session_key_states_v1.id"). Joins("LEFT JOIN app_session_key_applications_v1 ON app_session_key_applications_v1.session_key_state_id = app_session_key_states_v1.id"). - Where("app_session_key_states_v1.session_key = ? AND app_session_key_states_v1.version = (?) AND app_session_key_states_v1.expires_at > ? AND (app_session_key_app_sessions_v1.app_session_id = ? OR app_session_key_applications_v1.application_id = (?))", - sessionKey, maxVersionSubQ, time.Now().UTC(), appSessionId, appSubQuery). + Where("app_session_key_states_v1.session_key = ? AND c.version > 0 AND app_session_key_states_v1.expires_at > ? AND (app_session_key_app_sessions_v1.app_session_id = ? OR app_session_key_applications_v1.application_id = ?)", + sessionKey, time.Now().UTC(), appSessionId, applicationId). First(&dbState).Error if err != nil { if err == gorm.ErrRecordNotFound { - return "", fmt.Errorf("no active session key found for key %s and app session %s", sessionKey, appSessionId) + return "", fmt.Errorf("no active session key found for key %s, app session %s, application %s", sessionKey, appSessionId, applicationId) } return "", fmt.Errorf("failed to get session key owner: %w", err) } @@ -233,6 +257,7 @@ func dbSessionKeyStateToCore(dbState *AppSessionKeyStateV1) app.AppSessionKeySta UserAddress: dbState.UserAddress, SessionKey: dbState.SessionKey, Version: dbState.Version, + SessionKeySig: dbState.SessionKeySig, ApplicationIDs: applicationIDs, AppSessionIDs: appSessionIDs, ExpiresAt: dbState.ExpiresAt, diff --git a/clearnode/store/database/app_session_key_state_test.go b/nitronode/store/database/app_session_key_state_test.go similarity index 79% rename from clearnode/store/database/app_session_key_state_test.go rename to nitronode/store/database/app_session_key_state_test.go index 823f76049..25093d5c0 100644 --- a/clearnode/store/database/app_session_key_state_test.go +++ b/nitronode/store/database/app_session_key_state_test.go @@ -406,7 +406,7 @@ func TestDBStore_GetLastAppSessionKeyStates(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(stateB1)) - results, err := store.GetLastAppSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 2) @@ -455,7 +455,7 @@ func TestDBStore_GetLastAppSessionKeyStates(t *testing.T) { require.NoError(t, store.StoreAppSessionKeyState(stateB)) sessionKey := testKeyA - results, err := store.GetLastAppSessionKeyStates(testUser1, &sessionKey) + results, _, err := store.GetLastAppSessionKeyStates(testUser1, &sessionKey, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 1) @@ -488,7 +488,7 @@ func TestDBStore_GetLastAppSessionKeyStates(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(stateB)) - results, err := store.GetLastAppSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) // Both keys returned — caller is responsible for checking expiration @@ -501,7 +501,7 @@ func TestDBStore_GetLastAppSessionKeyStates(t *testing.T) { store := NewDBStore(db) - results, err := store.GetLastAppSessionKeyStates("0x0000000000000000000000000000000000000099", nil) + results, _, err := store.GetLastAppSessionKeyStates("0x0000000000000000000000000000000000000099", nil, true, 100, 0) require.NoError(t, err) assert.Empty(t, results) }) @@ -530,12 +530,198 @@ func TestDBStore_GetLastAppSessionKeyStates(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(state2)) - results, err := store.GetLastAppSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 1) assert.Equal(t, testUser1, results[0].UserAddress) }) + + t.Run("Pagination - limit and offset bound results, totalCount reflects unpaginated total", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + const numKeys = 5 + for i := 0; i < numKeys; i++ { + state := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: fakeSessionKey(i), + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + } + require.NoError(t, store.StoreAppSessionKeyState(state)) + } + + page1, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 2, 0) + require.NoError(t, err) + assert.Len(t, page1, 2) + assert.Equal(t, uint32(numKeys), total) + + page2, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 2, 2) + require.NoError(t, err) + assert.Len(t, page2, 2) + assert.Equal(t, uint32(numKeys), total) + + page3, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 2, 4) + require.NoError(t, err) + assert.Len(t, page3, 1) + assert.Equal(t, uint32(numKeys), total) + + seen := map[string]struct{}{} + for _, s := range page1 { + seen[s.SessionKey] = struct{}{} + } + for _, s := range page2 { + _, dup := seen[s.SessionKey] + assert.False(t, dup, "page2 overlaps page1 for %s", s.SessionKey) + seen[s.SessionKey] = struct{}{} + } + for _, s := range page3 { + _, dup := seen[s.SessionKey] + assert.False(t, dup, "page3 overlaps earlier page for %s", s.SessionKey) + } + }) + + t.Run("includeInactive=false filters out expired latest states and matches count", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + // Active latest + active := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsigA", + } + require.NoError(t, store.StoreAppSessionKeyState(active)) + + // Expired latest + expired := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyB, + Version: 1, + ExpiresAt: time.Now().Add(-1 * time.Hour), + UserSig: "0xsigB", + } + require.NoError(t, store.StoreAppSessionKeyState(expired)) + + results, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, false, 100, 0) + require.NoError(t, err) + assert.Len(t, results, 1) + assert.Equal(t, testKeyA, results[0].SessionKey) + assert.Equal(t, uint32(1), total) + + // includeInactive=true surfaces both, with count matching + all, allTotal, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 100, 0) + require.NoError(t, err) + assert.Len(t, all, 2) + assert.Equal(t, uint32(2), allTotal) + }) + + t.Run("Pagination - mixed active/expired with offset>0 keeps count and list consistent", func(t *testing.T) { + // Regression guard: with includeInactive=false the store must apply the + // expires_at filter to *both* the page slice and the unpaginated count + // (using the same `now` binding), otherwise pagination drifts when the + // caller walks past offset 0. + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + const numActive = 3 + const numExpired = 2 + for i := 0; i < numActive; i++ { + state := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: fakeSessionKey(i), + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + } + require.NoError(t, store.StoreAppSessionKeyState(state)) + } + for i := 0; i < numExpired; i++ { + state := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: fakeSessionKey(numActive + i), + Version: 1, + ExpiresAt: time.Now().Add(-1 * time.Hour), + UserSig: "0xsig", + } + require.NoError(t, store.StoreAppSessionKeyState(state)) + } + + page1, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, false, 2, 0) + require.NoError(t, err) + assert.Len(t, page1, 2) + assert.Equal(t, uint32(numActive), total) + + page2, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, false, 2, 2) + require.NoError(t, err) + assert.Len(t, page2, 1) + assert.Equal(t, uint32(numActive), total) + + empty, total, err := store.GetLastAppSessionKeyStates(testUser1, nil, false, 2, 4) + require.NoError(t, err) + assert.Empty(t, empty) + assert.Equal(t, uint32(numActive), total) + + seen := map[string]struct{}{} + for _, s := range append(append([]app.AppSessionKeyStateV1{}, page1...), page2...) { + assert.True(t, s.ExpiresAt.After(time.Now()), "expired state surfaced for %s", s.SessionKey) + _, dup := seen[s.SessionKey] + assert.False(t, dup, "duplicate session key %s across pages", s.SessionKey) + seen[s.SessionKey] = struct{}{} + } + + allPage1, allTotal, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 2, 0) + require.NoError(t, err) + assert.Len(t, allPage1, 2) + assert.Equal(t, uint32(numActive+numExpired), allTotal) + + allPage2, allTotal, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 2, 2) + require.NoError(t, err) + assert.Len(t, allPage2, 2) + assert.Equal(t, uint32(numActive+numExpired), allTotal) + + allPage3, allTotal, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 2, 4) + require.NoError(t, err) + assert.Len(t, allPage3, 1) + assert.Equal(t, uint32(numActive+numExpired), allTotal) + }) + + t.Run("includeInactive=false combined with session_key filter excludes the expired match", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + expired := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(-1 * time.Hour), + UserSig: "0xsigA", + } + require.NoError(t, store.StoreAppSessionKeyState(expired)) + + sessionKey := testKeyA + results, total, err := store.GetLastAppSessionKeyStates(testUser1, &sessionKey, false, 100, 0) + require.NoError(t, err) + assert.Empty(t, results) + assert.Equal(t, uint32(0), total) + + all, allTotal, err := store.GetLastAppSessionKeyStates(testUser1, &sessionKey, true, 100, 0) + require.NoError(t, err) + assert.Len(t, all, 1) + assert.Equal(t, uint32(1), allTotal) + }) } func TestDBStore_AppSessionKeyState_ForeignRelations(t *testing.T) { @@ -736,7 +922,7 @@ func TestDBStore_AppSessionKeyState_ForeignRelations(t *testing.T) { require.NoError(t, store.StoreAppSessionKeyState(stateB)) // GetLastAppSessionKeyStates returns both — verify preloaded relations are correct - results, err := store.GetLastAppSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastAppSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 2) @@ -878,7 +1064,7 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(state)) - owner, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1) + owner, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1, "poker") require.NoError(t, err) assert.Equal(t, testUser1, owner) }) @@ -917,7 +1103,33 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(state)) - owner, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1) + owner, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1, testApp1) + require.NoError(t, err) + assert.Equal(t, testUser1, owner) + }) + + t.Run("Success - Find owner by application ID when app session not yet persisted", func(t *testing.T) { + // Regression: during CreateAppSession the deterministic app session ID + // is not yet in the database. The owner lookup must still succeed via + // the application ID binding alone. + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + state := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 1, + ApplicationIDs: []string{testApp1}, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig123", + } + require.NoError(t, store.StoreAppSessionKeyState(state)) + + // Pass an app session ID that has not been inserted yet — the lookup + // must succeed using the application ID branch. + owner, err := store.GetAppSessionKeyOwner(testSessionKey, "0x0000000000000000000000000000000000000098", testApp1) require.NoError(t, err) assert.Equal(t, testUser1, owner) }) @@ -928,7 +1140,7 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { store := NewDBStore(db) - _, err := store.GetAppSessionKeyOwner("0x0000000000000000000000000000000000000099", "0x0000000000000000000000000000000000000098") + _, err := store.GetAppSessionKeyOwner("0x0000000000000000000000000000000000000099", "0x0000000000000000000000000000000000000098", "nonexistent-app") assert.Error(t, err) assert.Contains(t, err.Error(), "no active session key found") }) @@ -967,7 +1179,7 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(state)) - _, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1) + _, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1, "poker") assert.Error(t, err) assert.Contains(t, err.Error(), "no active session key found") }) @@ -1017,7 +1229,7 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { } require.NoError(t, store.StoreAppSessionKeyState(state2)) - owner, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1) + owner, err := store.GetAppSessionKeyOwner(testSessionKey, testSess1, "poker") require.NoError(t, err) assert.Equal(t, testUser1, owner) }) diff --git a/clearnode/store/database/app_session_test.go b/nitronode/store/database/app_session_test.go similarity index 85% rename from clearnode/store/database/app_session_test.go rename to nitronode/store/database/app_session_test.go index 0d8746b92..62e5bddbb 100644 --- a/clearnode/store/database/app_session_test.go +++ b/nitronode/store/database/app_session_test.go @@ -601,3 +601,65 @@ func TestDBStore_UpdateAppSession(t *testing.T) { assert.Contains(t, err.Error(), "concurrent modification detected") }) } + +func TestDBStore_GetAppSessionsCountByLabels(t *testing.T) { + t.Run("no sessions returns empty", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + results, err := store.GetAppSessionsCountByLabels() + require.NoError(t, err) + assert.Empty(t, results) + }) + + t.Run("counts grouped by application and status", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + now := time.Now() + require.NoError(t, db.Create(&AppSessionV1{ID: "s1", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: now}).Error) + require.NoError(t, db.Create(&AppSessionV1{ID: "s2", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 2, UpdatedAt: now}).Error) + require.NoError(t, db.Create(&AppSessionV1{ID: "s3", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusClosed, Nonce: 3, UpdatedAt: now}).Error) + require.NoError(t, db.Create(&AppSessionV1{ID: "s4", ApplicationID: "app2", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: now}).Error) + + results, err := store.GetAppSessionsCountByLabels() + require.NoError(t, err) + + countMap := make(map[string]uint64) + for _, r := range results { + countMap[r.Application+"/"+r.Status.String()] = r.Count + } + + assert.Equal(t, uint64(2), countMap["app1/"+app.AppSessionStatusOpen.String()]) + assert.Equal(t, uint64(1), countMap["app1/"+app.AppSessionStatusClosed.String()]) + assert.Equal(t, uint64(1), countMap["app2/"+app.AppSessionStatusOpen.String()]) + }) + + t.Run("reflects status transitions", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + now := time.Now() + require.NoError(t, db.Create(&AppSessionV1{ID: "s1", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, Version: 1, UpdatedAt: now}).Error) + require.NoError(t, db.Create(&AppSessionV1{ID: "s2", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 2, Version: 1, UpdatedAt: now}).Error) + + // Transition one session to closed + require.NoError(t, store.UpdateAppSession(app.AppSessionV1{ + SessionID: "s1", Status: app.AppSessionStatusClosed, SessionData: "{}", Version: 2, + })) + + results, err := store.GetAppSessionsCountByLabels() + require.NoError(t, err) + + countMap := make(map[string]uint64) + for _, r := range results { + countMap[r.Application+"/"+r.Status.String()] = r.Count + } + + assert.Equal(t, uint64(1), countMap["app1/"+app.AppSessionStatusOpen.String()]) + assert.Equal(t, uint64(1), countMap["app1/"+app.AppSessionStatusClosed.String()]) + }) +} diff --git a/clearnode/store/database/app_test.go b/nitronode/store/database/app_test.go similarity index 100% rename from clearnode/store/database/app_test.go rename to nitronode/store/database/app_test.go diff --git a/clearnode/store/database/blockchain_action.go b/nitronode/store/database/blockchain_action.go similarity index 91% rename from clearnode/store/database/blockchain_action.go rename to nitronode/store/database/blockchain_action.go index eaa69c374..3d5d66c7a 100644 --- a/clearnode/store/database/blockchain_action.go +++ b/nitronode/store/database/blockchain_action.go @@ -12,6 +12,7 @@ type BlockchainActionType uint8 const ( ActionTypeCheckpoint BlockchainActionType = 1 + ActionTypeChallenge BlockchainActionType = 2 ActionTypeInitiateEscrowDeposit BlockchainActionType = 10 ActionTypeFinalizeEscrowDeposit BlockchainActionType = 11 @@ -24,6 +25,8 @@ func (t BlockchainActionType) String() string { switch t { case ActionTypeCheckpoint: return "checkpoint" + case ActionTypeChallenge: + return "challenge" case ActionTypeInitiateEscrowDeposit: return "initiate_escrow_deposit" case ActionTypeFinalizeEscrowDeposit: @@ -68,6 +71,13 @@ func (s *DBStore) ScheduleCheckpoint(stateID string, blockchainID uint64) error return s.scheduleStateEnforcement(stateID, blockchainID, ActionTypeCheckpoint) } +// ScheduleChallenge queues a blockchain action to challenge a channel on its home blockchain +// using the provided state. The worker submits the state via challengeChannel(...) with a +// node-produced challenger signature. +func (s *DBStore) ScheduleChallenge(stateID string, blockchainID uint64) error { + return s.scheduleStateEnforcement(stateID, blockchainID, ActionTypeChallenge) +} + // ScheduleInitiateEscrowDeposit queues a blockchain action to initiate escrow deposit on home blockchain. func (s *DBStore) ScheduleInitiateEscrowDeposit(stateID string, blockchainID uint64) error { return s.scheduleStateEnforcement(stateID, blockchainID, ActionTypeInitiateEscrowDeposit) diff --git a/clearnode/store/database/blockchain_action_test.go b/nitronode/store/database/blockchain_action_test.go similarity index 94% rename from clearnode/store/database/blockchain_action_test.go rename to nitronode/store/database/blockchain_action_test.go index c94e24be7..1605b2a0d 100644 --- a/clearnode/store/database/blockchain_action_test.go +++ b/nitronode/store/database/blockchain_action_test.go @@ -34,7 +34,7 @@ func TestDBStore_ScheduleCheckpoint(t *testing.T) { UserBalance: decimal.NewFromInt(1000), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) err := store.ScheduleCheckpoint(state.ID, 0) require.NoError(t, err) @@ -73,7 +73,7 @@ func TestDBStore_ScheduleInitiateEscrowWithdrawal(t *testing.T) { UserBalance: decimal.NewFromInt(500), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) err := store.ScheduleInitiateEscrowWithdrawal(state.ID, 0) require.NoError(t, err) @@ -107,7 +107,7 @@ func TestDBStore_ScheduleFinalizeEscrowDeposit(t *testing.T) { UserBalance: decimal.NewFromInt(100), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) err := store.ScheduleFinalizeEscrowDeposit(state.ID, 0) require.NoError(t, err) @@ -139,7 +139,7 @@ func TestDBStore_ScheduleFinalizeEscrowWithdrawal(t *testing.T) { UserBalance: decimal.NewFromInt(200), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) err := store.ScheduleFinalizeEscrowWithdrawal(state.ID, 0) require.NoError(t, err) @@ -171,7 +171,7 @@ func TestDBStore_Fail(t *testing.T) { UserBalance: decimal.NewFromInt(300), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) require.NoError(t, store.ScheduleCheckpoint(state.ID, 0)) var action BlockchainAction @@ -211,7 +211,7 @@ func TestDBStore_FailNoRetry(t *testing.T) { UserBalance: decimal.NewFromInt(400), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) require.NoError(t, store.ScheduleCheckpoint(state.ID, 0)) var action BlockchainAction @@ -251,7 +251,7 @@ func TestDBStore_RecordAttempt(t *testing.T) { UserBalance: decimal.NewFromInt(150), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) require.NoError(t, store.ScheduleCheckpoint(state.ID, 0)) var action BlockchainAction @@ -291,7 +291,7 @@ func TestDBStore_Complete(t *testing.T) { UserBalance: decimal.NewFromInt(600), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) require.NoError(t, store.ScheduleCheckpoint(state.ID, 0)) var action BlockchainAction @@ -329,7 +329,7 @@ func TestDBStore_Complete(t *testing.T) { UserBalance: decimal.NewFromInt(250), }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) require.NoError(t, store.ScheduleCheckpoint(state.ID, 0)) var action BlockchainAction @@ -390,9 +390,9 @@ func TestDBStore_GetActions(t *testing.T) { HomeLedger: core.Ledger{UserBalance: decimal.NewFromInt(300)}, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) - require.NoError(t, store.StoreUserState(state3)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) + require.NoError(t, store.StoreUserState(state3, "")) require.NoError(t, store.ScheduleCheckpoint(state1.ID, 0)) require.NoError(t, store.ScheduleInitiateEscrowWithdrawal(state2.ID, 0)) @@ -425,7 +425,7 @@ func TestDBStore_GetActions(t *testing.T) { Transition: core.Transition{}, HomeLedger: core.Ledger{UserBalance: decimal.NewFromInt(int64(100 * (i + 1)))}, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) require.NoError(t, store.ScheduleCheckpoint(state.ID, 0)) } @@ -471,9 +471,9 @@ func TestDBStore_GetActions(t *testing.T) { HomeLedger: core.Ledger{UserBalance: decimal.NewFromInt(300)}, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) - require.NoError(t, store.StoreUserState(state3)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) + require.NoError(t, store.StoreUserState(state3, "")) require.NoError(t, store.ScheduleCheckpoint(state1.ID, 0)) require.NoError(t, store.ScheduleCheckpoint(state2.ID, 0)) diff --git a/clearnode/store/database/channel.go b/nitronode/store/database/channel.go similarity index 60% rename from clearnode/store/database/channel.go rename to nitronode/store/database/channel.go index 3979fa95a..33a94c45e 100644 --- a/clearnode/store/database/channel.go +++ b/nitronode/store/database/channel.go @@ -79,6 +79,10 @@ func (s *DBStore) GetChannelByID(channelID string) (*core.Channel, error) { } // GetActiveHomeChannel retrieves the active home channel for a user's wallet and asset. +// "Active" means the node has co-signed the channel definition (status Void or Open) — it +// does NOT guarantee the channel has been materialized onchain. Callers requiring onchain +// materialization (e.g., cross-chain escrow operations) must additionally check that +// Status == ChannelStatusOpen. func (s *DBStore) GetActiveHomeChannel(wallet, asset string) (*core.Channel, error) { var dbChannel Channel err := s.db. @@ -95,27 +99,72 @@ func (s *DBStore) GetActiveHomeChannel(wallet, asset string) (*core.Channel, err return databaseChannelToCore(&dbChannel), nil } -// CheckOpenChannel verifies if a user has an active channel for the given asset -// and returns the approved signature validators if such a channel exists. -func (s *DBStore) CheckOpenChannel(wallet, asset string) (string, bool, error) { - var approvedSigValidators string +// GetNotClosedHomeChannel retrieves the home channel for a user's wallet and asset as long +// as it has not reached ChannelStatusClosed. This is broader than GetActiveHomeChannel +// (which stops at Open) and is intended for read paths that must remain functional after +// an off-chain Finalize, such as fetching channel data before submitting an on-chain close. +func (s *DBStore) GetNotClosedHomeChannel(wallet, asset string) (*core.Channel, error) { + var dbChannel Channel + err := s.db. + Where("user_wallet = ? AND asset = ?", strings.ToLower(wallet), strings.ToLower(asset)). + Where("status != ? AND type = ?", core.ChannelStatusClosed, core.ChannelTypeHome). + First(&dbChannel).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("failed to get non-closed home channel: %w", err) + } + + return databaseChannelToCore(&dbChannel), nil +} + +// HasNonClosedHomeChannel returns true if any home channel for (wallet, asset) has a +// status other than Closed, meaning a channel lifecycle is still in progress. +func (s *DBStore) HasNonClosedHomeChannel(wallet, asset string) (bool, error) { + var count int64 + err := s.db.Model(&Channel{}). + Where("user_wallet = ? AND asset = ? AND type = ? AND status != ?", + strings.ToLower(wallet), strings.ToLower(asset), + core.ChannelTypeHome, core.ChannelStatusClosed). + Count(&count).Error + if err != nil { + return false, fmt.Errorf("failed to check non-closed home channel: %w", err) + } + return count > 0, nil +} + +// CheckActiveChannel verifies if a user has an active home channel for the given asset +// and returns its approved signature validators along with the channel status. +// "Active" includes Void (DB-only, awaiting onchain confirmation) and Open (materialized +// onchain). This is intentional: non-escrow offchain transitions (transfers, etc.) are +// permitted before onchain confirmation lands. Callers operating on cross-chain escrow +// flows that depend on onchain home-channel materialization must check that the returned +// status is ChannelStatusOpen. +// +// A nil status pointer means no active channel was found. +func (s *DBStore) CheckActiveChannel(wallet, asset string) (string, *core.ChannelStatus, error) { + var row struct { + ApprovedSigValidators string `gorm:"column:approved_sig_validators"` + Status core.ChannelStatus `gorm:"column:status"` + } result := s.db.Raw(` - SELECT approved_sig_validators + SELECT approved_sig_validators, status FROM channels WHERE user_wallet = ? AND asset = ? AND status <= ? AND type = ? LIMIT 1 - `, strings.ToLower(wallet), strings.ToLower(asset), core.ChannelStatusOpen, core.ChannelTypeHome).Scan(&approvedSigValidators) + `, strings.ToLower(wallet), strings.ToLower(asset), core.ChannelStatusOpen, core.ChannelTypeHome).Scan(&row) if result.Error != nil { - return "", false, fmt.Errorf("failed to check open channel: %w", result.Error) + return "", nil, fmt.Errorf("failed to check active channel: %w", result.Error) } if result.RowsAffected == 0 { - return "", false, nil + return "", nil, nil } - return approvedSigValidators, true, nil + return row.ApprovedSigValidators, &row.Status, nil } // GetUserChannels retrieves all channels for a user with optional status, asset, and type filters. @@ -152,6 +201,30 @@ 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"` +} + +// GetChannelsCountByLabels returns current channel counts grouped by asset and status. +func (s *DBStore) GetChannelsCountByLabels() ([]ChannelCount, error) { + var results []ChannelCount + err := s.db.Raw(` + SELECT asset, + status, + COUNT(channel_id) AS count + FROM channels + GROUP BY asset, status + `).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to get channel counts: %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/nitronode/store/database/channel_session_key_state.go b/nitronode/store/database/channel_session_key_state.go new file mode 100644 index 000000000..750b36cdb --- /dev/null +++ b/nitronode/store/database/channel_session_key_state.go @@ -0,0 +1,210 @@ +package database + +import ( + "fmt" + "strings" + "time" + + "github.com/layer-3/nitrolite/pkg/core" + "gorm.io/gorm" +) + +// ChannelSessionKeyStateV1 represents a channel session key state in the database. +type ChannelSessionKeyStateV1 struct { + ID string `gorm:"column:id;primaryKey"` + UserAddress string `gorm:"column:user_address;not null;uniqueIndex:idx_channel_session_key_states_v1_user_key_ver,priority:1"` + SessionKey string `gorm:"column:session_key;not null;uniqueIndex:idx_channel_session_key_states_v1_user_key_ver,priority:2"` + Version uint64 `gorm:"column:version;not null;uniqueIndex:idx_channel_session_key_states_v1_user_key_ver,priority:3"` + Assets []ChannelSessionKeyAssetV1 `gorm:"foreignKey:SessionKeyStateID;references:ID"` + MetadataHash string `gorm:"column:metadata_hash;type:char(66);not null"` + ExpiresAt time.Time `gorm:"column:expires_at;not null"` + UserSig string `gorm:"column:user_sig;not null"` + SessionKeySig string `gorm:"column:session_key_sig"` + CreatedAt time.Time +} + +func (ChannelSessionKeyStateV1) TableName() string { + return "channel_session_key_states_v1" +} + +// ChannelSessionKeyAssetV1 links a channel session key state to an asset. +type ChannelSessionKeyAssetV1 struct { + SessionKeyStateID string `gorm:"column:session_key_state_id;not null;primaryKey;priority:1"` + Asset string `gorm:"column:asset;not null;primaryKey;priority:2;index"` +} + +func (ChannelSessionKeyAssetV1) TableName() string { + return "channel_session_key_assets_v1" +} + +// StoreChannelSessionKeyState stores a new channel session key state version. +func (s *DBStore) StoreChannelSessionKeyState(state core.ChannelSessionKeyStateV1) error { + userAddress := strings.ToLower(state.UserAddress) + sessionKey := strings.ToLower(state.SessionKey) + + id, err := core.GenerateSessionKeyStateIDV1(userAddress, sessionKey, state.Version) + if err != nil { + return fmt.Errorf("failed to generate session key state ID: %w", err) + } + + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(userAddress, state.Version, state.Assets, state.ExpiresAt.Unix()) + if err != nil { + return fmt.Errorf("failed to compute metadata hash: %w", err) + } + + dbState := ChannelSessionKeyStateV1{ + ID: id, + UserAddress: userAddress, + SessionKey: sessionKey, + Version: state.Version, + MetadataHash: strings.ToLower(metadataHash.Hex()), + ExpiresAt: state.ExpiresAt.UTC(), + UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, + } + + if err := s.db.Create(&dbState).Error; err != nil { + return fmt.Errorf("failed to store channel session key state: %w", err) + } + + if len(state.Assets) > 0 { + assets := make([]ChannelSessionKeyAssetV1, len(state.Assets)) + for i, asset := range state.Assets { + assets[i] = ChannelSessionKeyAssetV1{ + SessionKeyStateID: id, + Asset: strings.ToLower(asset), + } + } + if err := s.db.Create(&assets).Error; err != nil { + return fmt.Errorf("failed to store channel session key assets: %w", err) + } + } + + if err := upsertCurrentSessionKeyState(s.db, userAddress, sessionKey, SessionKeyKindChannel, state.Version); err != nil { + return err + } + + return nil +} + +// GetLastChannelSessionKeyStates retrieves the latest channel session key states for a user with optional filtering. +// Reads filter the current_session_key_states_v1 pointer table by (user_address, kind=channel) +// and JOIN history on (user_address, session_key, version). Per-request DB work is bounded by +// the number of distinct session keys for the user, regardless of version churn in history. +// When includeInactive is false the same now is applied to both the count and the list query so +// pagination stays consistent across the two reads. Results are paginated; totalCount is the +// unpaginated total of matching session keys. +func (s *DBStore) GetLastChannelSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]core.ChannelSessionKeyStateV1, uint32, error) { + wallet = strings.ToLower(wallet) + now := time.Now().UTC() + + pointerQuery := s.db.Table("current_session_key_states_v1 AS c"). + Where("c.user_address = ? AND c.kind = ? AND c.version > 0", wallet, SessionKeyKindChannel) + if sessionKey != nil && *sessionKey != "" { + pointerQuery = pointerQuery.Where("c.session_key = ?", strings.ToLower(*sessionKey)) + } + if !includeInactive { + pointerQuery = pointerQuery. + Joins("JOIN channel_session_key_states_v1 h ON h.user_address = c.user_address AND h.session_key = c.session_key AND h.version = c.version"). + Where("h.expires_at > ?", now) + } + + var totalCount int64 + if err := pointerQuery.Count(&totalCount).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count channel session key states: %w", err) + } + + query := s.db.Model(&ChannelSessionKeyStateV1{}). + Joins("JOIN current_session_key_states_v1 c ON c.user_address = channel_session_key_states_v1.user_address AND c.session_key = channel_session_key_states_v1.session_key AND c.version = channel_session_key_states_v1.version"). + Where("c.user_address = ? AND c.kind = ? AND c.version > 0", wallet, SessionKeyKindChannel). + Preload("Assets"). + Order("channel_session_key_states_v1.created_at DESC, channel_session_key_states_v1.id ASC"). + Limit(int(limit)). + Offset(int(offset)) + if sessionKey != nil && *sessionKey != "" { + query = query.Where("c.session_key = ?", strings.ToLower(*sessionKey)) + } + if !includeInactive { + query = query.Where("channel_session_key_states_v1.expires_at > ?", now) + } + + var dbStates []ChannelSessionKeyStateV1 + if err := query.Find(&dbStates).Error; err != nil { + return nil, 0, fmt.Errorf("failed to get channel session key states: %w", err) + } + + states := make([]core.ChannelSessionKeyStateV1, len(dbStates)) + for i, dbState := range dbStates { + states[i] = dbChannelSessionKeyStateToCore(&dbState) + } + + return states, uint32(totalCount), nil +} + +// GetLastChannelSessionKeyVersion returns the latest version of a channel session key state. +// Reads from the pointer table; returns 0 if no state exists or the pointer is at its seeded +// value (LockSessionKeyState created the row but no submit has succeeded yet). +func (s *DBStore) GetLastChannelSessionKeyVersion(wallet, sessionKey string) (uint64, error) { + wallet = strings.ToLower(wallet) + sessionKey = strings.ToLower(sessionKey) + + var pointer CurrentSessionKeyStateV1 + err := s.db. + Where("user_address = ? AND session_key = ? AND kind = ?", wallet, sessionKey, SessionKeyKindChannel). + Take(&pointer).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { + return 0, nil + } + return 0, fmt.Errorf("failed to check channel session key state: %w", err) + } + + return pointer.Version, nil +} + +// ValidateChannelSessionKeyForAsset checks in a single query that: +// - a session key state exists for the (wallet, sessionKey) pair, +// - it is the latest version, +// - it is not expired, +// - the asset is in the allowed list, +// - the metadata hash matches. +func (s *DBStore) ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) { + wallet = strings.ToLower(wallet) + sessionKey = strings.ToLower(sessionKey) + asset = strings.ToLower(asset) + metadataHash = strings.ToLower(metadataHash) + + now := time.Now().UTC() + + var count int64 + err := s.db.Model(&ChannelSessionKeyStateV1{}). + Joins("JOIN current_session_key_states_v1 c ON c.user_address = channel_session_key_states_v1.user_address AND c.session_key = channel_session_key_states_v1.session_key AND c.version = channel_session_key_states_v1.version AND c.kind = ?", SessionKeyKindChannel). + Joins("JOIN channel_session_key_assets_v1 ON channel_session_key_assets_v1.session_key_state_id = channel_session_key_states_v1.id AND channel_session_key_assets_v1.asset = ?", asset). + Where("channel_session_key_states_v1.user_address = ? AND channel_session_key_states_v1.session_key = ? AND channel_session_key_states_v1.expires_at > ? AND channel_session_key_states_v1.metadata_hash = ?", + wallet, sessionKey, now, metadataHash). + Count(&count).Error + + if err != nil { + return false, fmt.Errorf("failed to validate session key for asset: %w", err) + } + + return count > 0, nil +} + +func dbChannelSessionKeyStateToCore(dbState *ChannelSessionKeyStateV1) core.ChannelSessionKeyStateV1 { + assets := make([]string, len(dbState.Assets)) + for i, a := range dbState.Assets { + assets[i] = a.Asset + } + + return core.ChannelSessionKeyStateV1{ + UserAddress: dbState.UserAddress, + SessionKey: dbState.SessionKey, + Version: dbState.Version, + Assets: assets, + ExpiresAt: dbState.ExpiresAt, + UserSig: dbState.UserSig, + SessionKeySig: dbState.SessionKeySig, + } +} diff --git a/clearnode/store/database/channel_session_key_state_test.go b/nitronode/store/database/channel_session_key_state_test.go similarity index 75% rename from clearnode/store/database/channel_session_key_state_test.go rename to nitronode/store/database/channel_session_key_state_test.go index 4263cc63d..9781ca0e8 100644 --- a/clearnode/store/database/channel_session_key_state_test.go +++ b/nitronode/store/database/channel_session_key_state_test.go @@ -44,7 +44,7 @@ func TestDBStore_StoreChannelSessionKeyState(t *testing.T) { require.NoError(t, err) // Verify via GetLastChannelSessionKeyStates - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) require.Len(t, results, 1) @@ -75,7 +75,7 @@ func TestDBStore_StoreChannelSessionKeyState(t *testing.T) { err := store.StoreChannelSessionKeyState(state) require.NoError(t, err) - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) require.Len(t, results, 1) assert.Empty(t, results[0].Assets) @@ -100,7 +100,7 @@ func TestDBStore_StoreChannelSessionKeyState(t *testing.T) { require.NoError(t, err) // Query with mixed case - should still find it - results, err := store.GetLastChannelSessionKeyStates("0xAbCdEf1234567890AbCdEf1234567890AbCdEf12", nil) + results, _, err := store.GetLastChannelSessionKeyStates("0xAbCdEf1234567890AbCdEf1234567890AbCdEf12", nil, true, 100, 0) require.NoError(t, err) require.Len(t, results, 1) assert.Equal(t, "0xabcdef1234567890abcdef1234567890abcdef12", results[0].UserAddress) @@ -157,7 +157,7 @@ func TestDBStore_StoreChannelSessionKeyState(t *testing.T) { require.NoError(t, store.StoreChannelSessionKeyState(state2)) // Should return only the latest version - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) require.Len(t, results, 1) assert.Equal(t, uint64(2), results[0].Version) @@ -223,7 +223,7 @@ func TestDBStore_GetLastChannelSessionKeyStates(t *testing.T) { } require.NoError(t, store.StoreChannelSessionKeyState(stateB1)) - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 2) @@ -272,7 +272,7 @@ func TestDBStore_GetLastChannelSessionKeyStates(t *testing.T) { require.NoError(t, store.StoreChannelSessionKeyState(stateB)) sessionKey := testKeyA - results, err := store.GetLastChannelSessionKeyStates(testUser1, &sessionKey) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, &sessionKey, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 1) @@ -305,7 +305,7 @@ func TestDBStore_GetLastChannelSessionKeyStates(t *testing.T) { } require.NoError(t, store.StoreChannelSessionKeyState(stateB)) - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) // Both keys returned — caller is responsible for checking expiration @@ -318,7 +318,7 @@ func TestDBStore_GetLastChannelSessionKeyStates(t *testing.T) { store := NewDBStore(db) - results, err := store.GetLastChannelSessionKeyStates("0x0000000000000000000000000000000000000099", nil) + results, _, err := store.GetLastChannelSessionKeyStates("0x0000000000000000000000000000000000000099", nil, true, 100, 0) require.NoError(t, err) assert.Empty(t, results) }) @@ -347,12 +347,212 @@ func TestDBStore_GetLastChannelSessionKeyStates(t *testing.T) { } require.NoError(t, store.StoreChannelSessionKeyState(state2)) - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 1) assert.Equal(t, testUser1, results[0].UserAddress) }) + + t.Run("Pagination - limit and offset bound results, totalCount reflects unpaginated total", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + // Insert 5 distinct session keys for testUser1 + const numKeys = 5 + for i := 0; i < numKeys; i++ { + state := core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: fakeSessionKey(i), + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + } + require.NoError(t, store.StoreChannelSessionKeyState(state)) + } + + // First page: 2 of 5 + page1, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 2, 0) + require.NoError(t, err) + assert.Len(t, page1, 2) + assert.Equal(t, uint32(numKeys), total) + + // Second page: 2 of 5 + page2, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 2, 2) + require.NoError(t, err) + assert.Len(t, page2, 2) + assert.Equal(t, uint32(numKeys), total) + + // Third page: 1 of 5 + page3, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 2, 4) + require.NoError(t, err) + assert.Len(t, page3, 1) + assert.Equal(t, uint32(numKeys), total) + + // Pages must not overlap + seen := map[string]struct{}{} + for _, s := range page1 { + seen[s.SessionKey] = struct{}{} + } + for _, s := range page2 { + _, dup := seen[s.SessionKey] + assert.False(t, dup, "page2 overlaps page1 for %s", s.SessionKey) + seen[s.SessionKey] = struct{}{} + } + for _, s := range page3 { + _, dup := seen[s.SessionKey] + assert.False(t, dup, "page3 overlaps earlier page for %s", s.SessionKey) + } + }) + + t.Run("includeInactive=false filters out expired latest states and matches count", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + active := core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + Assets: []string{testAsset1}, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsigA", + } + require.NoError(t, store.StoreChannelSessionKeyState(active)) + + expired := core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyB, + Version: 1, + Assets: []string{testAsset2}, + ExpiresAt: time.Now().Add(-1 * time.Hour), + UserSig: "0xsigB", + } + require.NoError(t, store.StoreChannelSessionKeyState(expired)) + + results, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, false, 100, 0) + require.NoError(t, err) + assert.Len(t, results, 1) + assert.Equal(t, testKeyA, results[0].SessionKey) + assert.Equal(t, uint32(1), total) + + all, allTotal, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) + require.NoError(t, err) + assert.Len(t, all, 2) + assert.Equal(t, uint32(2), allTotal) + }) + + t.Run("Pagination - mixed active/expired with offset>0 keeps count and list consistent", func(t *testing.T) { + // Regression guard: with includeInactive=false the store must apply the + // expires_at filter to *both* the page slice and the unpaginated count + // (using the same `now` binding), otherwise pagination drifts when the + // caller walks past offset 0. + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + const numActive = 3 + const numExpired = 2 + for i := 0; i < numActive; i++ { + state := core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: fakeSessionKey(i), + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + } + require.NoError(t, store.StoreChannelSessionKeyState(state)) + } + for i := 0; i < numExpired; i++ { + state := core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: fakeSessionKey(numActive + i), + Version: 1, + ExpiresAt: time.Now().Add(-1 * time.Hour), + UserSig: "0xsig", + } + require.NoError(t, store.StoreChannelSessionKeyState(state)) + } + + // includeInactive=false: count reflects active-only total across all pages. + page1, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, false, 2, 0) + require.NoError(t, err) + assert.Len(t, page1, 2) + assert.Equal(t, uint32(numActive), total) + + page2, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, false, 2, 2) + require.NoError(t, err) + assert.Len(t, page2, 1) + assert.Equal(t, uint32(numActive), total) + + // Asking past the active-only total returns empty without changing count. + empty, total, err := store.GetLastChannelSessionKeyStates(testUser1, nil, false, 2, 4) + require.NoError(t, err) + assert.Empty(t, empty) + assert.Equal(t, uint32(numActive), total) + + // None of the paged rows may be expired, and pages must not overlap. + seen := map[string]struct{}{} + for _, s := range append(append([]core.ChannelSessionKeyStateV1{}, page1...), page2...) { + assert.True(t, s.ExpiresAt.After(time.Now()), "expired state surfaced for %s", s.SessionKey) + _, dup := seen[s.SessionKey] + assert.False(t, dup, "duplicate session key %s across pages", s.SessionKey) + seen[s.SessionKey] = struct{}{} + } + + // includeInactive=true: count reflects every latest state, paging through reaches both buckets. + allPage1, allTotal, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 2, 0) + require.NoError(t, err) + assert.Len(t, allPage1, 2) + assert.Equal(t, uint32(numActive+numExpired), allTotal) + + allPage2, allTotal, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 2, 2) + require.NoError(t, err) + assert.Len(t, allPage2, 2) + assert.Equal(t, uint32(numActive+numExpired), allTotal) + + allPage3, allTotal, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 2, 4) + require.NoError(t, err) + assert.Len(t, allPage3, 1) + assert.Equal(t, uint32(numActive+numExpired), allTotal) + }) + + t.Run("includeInactive=false combined with session_key filter excludes the expired match", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + expired := core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + Assets: []string{testAsset1}, + ExpiresAt: time.Now().Add(-1 * time.Hour), + UserSig: "0xsigA", + } + require.NoError(t, store.StoreChannelSessionKeyState(expired)) + + sessionKey := testKeyA + results, total, err := store.GetLastChannelSessionKeyStates(testUser1, &sessionKey, false, 100, 0) + require.NoError(t, err) + assert.Empty(t, results) + assert.Equal(t, uint32(0), total) + + all, allTotal, err := store.GetLastChannelSessionKeyStates(testUser1, &sessionKey, true, 100, 0) + require.NoError(t, err) + assert.Len(t, all, 1) + assert.Equal(t, uint32(1), allTotal) + }) +} + +// fakeSessionKey returns a deterministic 20-byte hex address for the given index. +func fakeSessionKey(i int) string { + return strings.ToLower("0x" + strings.Repeat("0", 38) + string("0123456789abcdef"[i%16]) + string("0123456789abcdef"[(i/16)%16])) } func TestDBStore_GetLastChannelSessionKeyVersion(t *testing.T) { @@ -453,7 +653,7 @@ func TestDBStore_ValidateChannelSessionKeyForAsset(t *testing.T) { // Helper to compute metadata hash for a given state computeMetadataHash := func(t *testing.T, version uint64, assets []string, expiresAt time.Time) string { t.Helper() - hash, err := core.GetChannelSessionKeyAuthMetadataHashV1(version, assets, expiresAt.Unix()) + hash, err := core.GetChannelSessionKeyAuthMetadataHashV1(testUser1, version, assets, expiresAt.Unix()) require.NoError(t, err) return strings.ToLower(hash.Hex()) } @@ -802,7 +1002,7 @@ func TestDBStore_ChannelSessionKeyState_ForeignRelations(t *testing.T) { require.NoError(t, store.StoreChannelSessionKeyState(stateB)) // GetLastChannelSessionKeyStates returns both — verify preloaded relations are correct - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 2) @@ -854,7 +1054,7 @@ func TestDBStore_ChannelSessionKeyState_ForeignRelations(t *testing.T) { } require.NoError(t, store.StoreChannelSessionKeyState(stateB)) - results, err := store.GetLastChannelSessionKeyStates(testUser1, nil) + results, _, err := store.GetLastChannelSessionKeyStates(testUser1, nil, true, 100, 0) require.NoError(t, err) assert.Len(t, results, 2) diff --git a/clearnode/store/database/channel_test.go b/nitronode/store/database/channel_test.go similarity index 68% rename from clearnode/store/database/channel_test.go rename to nitronode/store/database/channel_test.go index 0be5d2559..54ce7aa07 100644 --- a/clearnode/store/database/channel_test.go +++ b/nitronode/store/database/channel_test.go @@ -234,7 +234,7 @@ func TestDBStore_GetActiveHomeChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetActiveHomeChannel("0xuser123", "USDC") require.NoError(t, err) @@ -295,7 +295,7 @@ func TestDBStore_GetActiveHomeChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetActiveHomeChannel("0xuser123", "USDC") require.NoError(t, err) @@ -341,7 +341,7 @@ func TestDBStore_GetActiveHomeChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetActiveHomeChannel("0xuser123", "USDC") require.NoError(t, err) @@ -369,7 +369,7 @@ func TestDBStore_GetActiveHomeChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetActiveHomeChannel("0xuser123", "USDC") require.NoError(t, err) @@ -377,7 +377,72 @@ func TestDBStore_GetActiveHomeChannel(t *testing.T) { }) } -func TestDBStore_CheckOpenChannel(t *testing.T) { +func TestDBStore_GetNotClosedHomeChannel(t *testing.T) { + makeChannel := func(id, wallet, asset string, status core.ChannelStatus, chType core.ChannelType) core.Channel { + return core.Channel{ + ChannelID: id, + UserWallet: wallet, + Asset: asset, + Type: chType, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: status, + StateVersion: 1, + } + } + + for _, tc := range []struct { + name string + status core.ChannelStatus + expectFound bool + }{ + {"returns Void channel", core.ChannelStatusVoid, true}, + {"returns Open channel", core.ChannelStatusOpen, true}, + {"returns Challenged channel", core.ChannelStatusChallenged, true}, + {"returns Closing channel", core.ChannelStatusClosing, true}, + {"returns nil for Closed channel", core.ChannelStatusClosed, false}, + } { + t.Run(tc.name, func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.CreateChannel(makeChannel("0xch1", "0xuser123", "usdc", tc.status, core.ChannelTypeHome))) + + result, err := store.GetNotClosedHomeChannel("0xuser123", "USDC") + require.NoError(t, err) + if tc.expectFound { + require.NotNil(t, result) + assert.Equal(t, tc.status, result.Status) + } else { + assert.Nil(t, result) + } + }) + } + + t.Run("returns nil when no channel exists", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + result, err := NewDBStore(db).GetNotClosedHomeChannel("0xuser123", "USDC") + require.NoError(t, err) + assert.Nil(t, result) + }) + + t.Run("ignores escrow channels", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(makeChannel("0xesc1", "0xuser123", "usdc", core.ChannelStatusOpen, core.ChannelTypeEscrow))) + result, err := store.GetNotClosedHomeChannel("0xuser123", "USDC") + require.NoError(t, err) + assert.Nil(t, result) + }) +} + + +func TestDBStore_CheckActiveChannel(t *testing.T) { t.Run("Success - Has open channel", func(t *testing.T) { db, cleanup := SetupTestDB(t) defer cleanup() @@ -418,27 +483,28 @@ func TestDBStore_CheckOpenChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) - approvedSigValidators, hasOpenChannel, err := store.CheckOpenChannel("0xuser123", "USDC") + approvedSigValidators, status, err := store.CheckActiveChannel("0xuser123", "USDC") require.NoError(t, err) - assert.True(t, hasOpenChannel) + require.NotNil(t, status) + assert.Equal(t, core.ChannelStatusOpen, *status) assert.Equal(t, "0x2", approvedSigValidators) }) - t.Run("No open channel - user not found", func(t *testing.T) { + t.Run("No active channel - user not found", func(t *testing.T) { db, cleanup := SetupTestDB(t) defer cleanup() store := NewDBStore(db) - approvedSigValidators, hasOpenChannel, err := store.CheckOpenChannel("0xnonexistent", "USDC") + approvedSigValidators, status, err := store.CheckActiveChannel("0xnonexistent", "USDC") require.NoError(t, err) - assert.False(t, hasOpenChannel) + assert.Nil(t, status) assert.Equal(t, "", approvedSigValidators) }) - t.Run("No open channel - channel is closed", func(t *testing.T) { + t.Run("No active channel - channel is closed", func(t *testing.T) { db, cleanup := SetupTestDB(t) defer cleanup() @@ -477,15 +543,15 @@ func TestDBStore_CheckOpenChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) - approvedSigValidators, hasOpenChannel, err := store.CheckOpenChannel("0xuser123", "USDC") + approvedSigValidators, status, err := store.CheckActiveChannel("0xuser123", "USDC") require.NoError(t, err) - assert.False(t, hasOpenChannel) + assert.Nil(t, status) assert.Equal(t, "", approvedSigValidators) }) - t.Run("No open channel - wrong asset", func(t *testing.T) { + t.Run("No active channel - wrong asset", func(t *testing.T) { db, cleanup := SetupTestDB(t) defer cleanup() @@ -524,14 +590,52 @@ func TestDBStore_CheckOpenChannel(t *testing.T) { NodeNetFlow: decimal.Zero, }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) // Check for different asset - approvedSigValidators, hasOpenChannel, err := store.CheckOpenChannel("0xuser123", "ETH") + approvedSigValidators, status, err := store.CheckActiveChannel("0xuser123", "ETH") require.NoError(t, err) - assert.False(t, hasOpenChannel) + assert.Nil(t, status) assert.Equal(t, "", approvedSigValidators) }) + + // These two cases pin the status <= ChannelStatusOpen invariant: Closing and Challenged + // channels must not be returned as active, so post-finalize state submissions are rejected. + for _, tc := range []struct { + name string + status core.ChannelStatus + }{ + {"No active channel - channel is closing", core.ChannelStatusClosing}, + {"No active channel - channel is challenged", core.ChannelStatusChallenged}, + } { + t.Run(tc.name, func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + + channel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: tc.status, + StateVersion: 1, + } + require.NoError(t, store.CreateChannel(channel)) + + approvedSigValidators, status, err := store.CheckActiveChannel("0xuser123", "USDC") + require.NoError(t, err) + assert.Nil(t, status) + assert.Equal(t, "", approvedSigValidators) + }) + } } func TestDBStore_UpdateChannel(t *testing.T) { @@ -822,3 +926,183 @@ func TestDBStore_GetUserChannels(t *testing.T) { assert.Equal(t, uint32(0), total) }) } + +func TestDBStore_GetChannelsCountByLabels(t *testing.T) { + t.Run("no channels returns empty", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + results, err := store.GetChannelsCountByLabels() + require.NoError(t, err) + assert.Empty(t, results) + }) + + t.Run("counts grouped by asset and status", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + for i := 0; i < 3; i++ { + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: fmt.Sprintf("0xchannel_open_usdc_%d", i), UserWallet: "0xuser1", Asset: "usdc", + Type: core.ChannelTypeHome, BlockchainID: 1, ChallengeDuration: 86400, Nonce: uint64(i + 1), + Status: core.ChannelStatusOpen, + })) + } + for i := 0; i < 2; i++ { + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: fmt.Sprintf("0xchannel_closed_usdc_%d", i), UserWallet: "0xuser1", Asset: "usdc", + Type: core.ChannelTypeHome, BlockchainID: 1, ChallengeDuration: 86400, Nonce: uint64(i + 10), + Status: core.ChannelStatusClosed, + })) + } + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: "0xchannel_open_eth_0", UserWallet: "0xuser2", Asset: "eth", + Type: core.ChannelTypeHome, BlockchainID: 1, ChallengeDuration: 86400, Nonce: 1, + Status: core.ChannelStatusOpen, + })) + + results, err := store.GetChannelsCountByLabels() + require.NoError(t, err) + + countMap := make(map[string]uint64) + for _, r := range results { + countMap[r.Asset+"/"+r.Status.String()] = r.Count + } + + assert.Equal(t, uint64(3), countMap["usdc/"+core.ChannelStatusOpen.String()]) + assert.Equal(t, uint64(2), countMap["usdc/"+core.ChannelStatusClosed.String()]) + assert.Equal(t, uint64(1), countMap["eth/"+core.ChannelStatusOpen.String()]) + }) + + t.Run("reflects status transitions", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: "0xchannel1", UserWallet: "0xuser1", Asset: "usdc", + Type: core.ChannelTypeHome, BlockchainID: 1, ChallengeDuration: 86400, Nonce: 1, + Status: core.ChannelStatusOpen, + })) + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: "0xchannel2", UserWallet: "0xuser1", Asset: "usdc", + Type: core.ChannelTypeHome, BlockchainID: 1, ChallengeDuration: 86400, Nonce: 2, + Status: core.ChannelStatusOpen, + })) + + // Transition one channel to closed + ch1, err := store.GetChannelByID("0xchannel1") + require.NoError(t, err) + ch1.Status = core.ChannelStatusClosed + require.NoError(t, store.UpdateChannel(*ch1)) + + results, err := store.GetChannelsCountByLabels() + require.NoError(t, err) + + countMap := make(map[string]uint64) + for _, r := range results { + countMap[r.Asset+"/"+r.Status.String()] = r.Count + } + + assert.Equal(t, uint64(1), countMap["usdc/"+core.ChannelStatusOpen.String()]) + assert.Equal(t, uint64(1), countMap["usdc/"+core.ChannelStatusClosed.String()]) + }) +} + +func TestDBStore_HasNonClosedHomeChannel(t *testing.T) { + newChannel := func(id, wallet, asset string, status core.ChannelStatus, nonce uint64) core.Channel { + return core.Channel{ + ChannelID: id, + UserWallet: wallet, + Asset: asset, + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken", + ChallengeDuration: 86400, + Nonce: nonce, + Status: status, + } + } + + t.Run("no channels returns false", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + result, err := store.HasNonClosedHomeChannel("0xuser", "USDC") + require.NoError(t, err) + assert.False(t, result) + }) + + t.Run("only closed channel returns false", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.CreateChannel(newChannel("0xch1", "0xuser", "usdc", core.ChannelStatusClosed, 1))) + + result, err := store.HasNonClosedHomeChannel("0xuser", "USDC") + require.NoError(t, err) + assert.False(t, result) + }) + + for _, status := range []core.ChannelStatus{ + core.ChannelStatusVoid, + core.ChannelStatusOpen, + core.ChannelStatusChallenged, + core.ChannelStatusClosing, + } { + status := status + t.Run("returns true for status "+status.String(), func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.CreateChannel(newChannel("0xch1", "0xuser", "usdc", status, 1))) + + result, err := store.HasNonClosedHomeChannel("0xuser", "USDC") + require.NoError(t, err) + assert.True(t, result) + }) + } + + t.Run("escrow channel does not count", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + escrow := newChannel("0xch1", "0xuser", "usdc", core.ChannelStatusOpen, 1) + escrow.Type = core.ChannelTypeEscrow + require.NoError(t, store.CreateChannel(escrow)) + + result, err := store.HasNonClosedHomeChannel("0xuser", "USDC") + require.NoError(t, err) + assert.False(t, result) + }) + + t.Run("different wallet is not counted", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.CreateChannel(newChannel("0xch1", "0xother", "usdc", core.ChannelStatusOpen, 1))) + + result, err := store.HasNonClosedHomeChannel("0xuser", "USDC") + require.NoError(t, err) + assert.False(t, result) + }) + + t.Run("wallet lookup is case-insensitive", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.CreateChannel(newChannel("0xch1", "0xUser123", "usdc", core.ChannelStatusClosing, 1))) + + result, err := store.HasNonClosedHomeChannel("0xuser123", "USDC") + require.NoError(t, err) + assert.True(t, result) + }) +} diff --git a/clearnode/store/database/contract_event.go b/nitronode/store/database/contract_event.go similarity index 57% rename from clearnode/store/database/contract_event.go rename to nitronode/store/database/contract_event.go index 955feb58d..cdc5af2db 100644 --- a/clearnode/store/database/contract_event.go +++ b/nitronode/store/database/contract_event.go @@ -34,7 +34,7 @@ func (s *DBStore) StoreContractEvent(ev core.BlockchainEvent) error { BlockchainID: ev.BlockchainID, Name: ev.Name, BlockNumber: ev.BlockNumber, - TransactionHash: ev.TransactionHash, + TransactionHash: strings.ToLower(ev.TransactionHash), LogIndex: ev.LogIndex, CreatedAt: time.Now(), } @@ -42,29 +42,30 @@ func (s *DBStore) StoreContractEvent(ev core.BlockchainEvent) error { return s.db.Create(contractEvent).Error } -// GetLatestEvent returns the latest block number and log index for a given contract. -// This function matches the signature required by pkg/blockchain/evm.GetLatestEvent. -func (s *DBStore) GetLatestEvent(contractAddress string, blockchainID uint64) (core.BlockchainEvent, error) { - var ev ContractEvent - err := s.db.Where("blockchain_id = ? AND contract_address = ?", blockchainID, strings.ToLower(contractAddress)). - Order("block_number DESC, log_index DESC"). - First(&ev).Error +// GetLatestContractEventBlockNumber returns the highest block number stored for a given contract. +func (s *DBStore) GetLatestContractEventBlockNumber(contractAddress string, blockchainID uint64) (uint64, error) { + var blockNumber uint64 + err := s.db.Model(&ContractEvent{}). + Where("blockchain_id = ? AND contract_address = ?", blockchainID, strings.ToLower(contractAddress)). + Select("COALESCE(MAX(block_number), 0)"). + Scan(&blockNumber).Error + if err != nil { + return 0, err + } + return blockNumber, nil +} +// IsContractEventPresent checks whether a specific contract event has already been stored. +func (s *DBStore) IsContractEventPresent(blockchainID, blockNumber uint64, txHash string, logIndex uint32) (bool, error) { + var ev ContractEvent + err := s.db.Where("blockchain_id = ? AND block_number = ? AND transaction_hash = ? AND log_index = ?", + blockchainID, blockNumber, strings.ToLower(txHash), logIndex). + Take(&ev).Error if errors.Is(err, gorm.ErrRecordNotFound) { - // No events found, return zeros (will start from beginning) - return core.BlockchainEvent{}, nil + return false, nil } - if err != nil { - return core.BlockchainEvent{}, err + return false, err } - - return core.BlockchainEvent{ - BlockNumber: ev.BlockNumber, - BlockchainID: ev.BlockchainID, - Name: ev.Name, - ContractAddress: ev.ContractAddress, - TransactionHash: ev.TransactionHash, - LogIndex: ev.LogIndex, - }, nil + return true, nil } diff --git a/nitronode/store/database/contract_event_test.go b/nitronode/store/database/contract_event_test.go new file mode 100644 index 000000000..11a28ab02 --- /dev/null +++ b/nitronode/store/database/contract_event_test.go @@ -0,0 +1,138 @@ +package database + +import ( + "testing" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStoreContractEvent(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + event := core.BlockchainEvent{ + ContractAddress: "0x1234567890123456789012345678901234567890", + BlockchainID: 1, + Name: "HomeChannelCreated", + BlockNumber: 100, + TransactionHash: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + LogIndex: 5, + } + + err := store.StoreContractEvent(event) + require.NoError(t, err) + + // Verify the event was stored + var storedEvent ContractEvent + err = db.Where("transaction_hash = ? AND log_index = ?", event.TransactionHash, event.LogIndex).First(&storedEvent).Error + require.NoError(t, err) + + assert.Equal(t, event.ContractAddress, storedEvent.ContractAddress) + assert.Equal(t, event.BlockchainID, storedEvent.BlockchainID) + assert.Equal(t, event.Name, storedEvent.Name) + assert.Equal(t, event.BlockNumber, storedEvent.BlockNumber) + assert.Equal(t, event.TransactionHash, storedEvent.TransactionHash) + assert.Equal(t, event.LogIndex, storedEvent.LogIndex) +} + +func TestGetLatestContractEventBlockNumber(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + contractAddress := "0x1234567890123456789012345678901234567890" + blockchainID := uint64(1) + + t.Run("no events returns zero", func(t *testing.T) { + block, err := store.GetLatestContractEventBlockNumber(contractAddress, blockchainID) + require.NoError(t, err) + assert.Equal(t, uint64(0), block) + }) + + t.Run("returns max block number across multiple events", func(t *testing.T) { + events := []core.BlockchainEvent{ + {ContractAddress: contractAddress, BlockchainID: blockchainID, Name: "E1", BlockNumber: 100, TransactionHash: "0xaaa", LogIndex: 0}, + {ContractAddress: contractAddress, BlockchainID: blockchainID, Name: "E2", BlockNumber: 200, TransactionHash: "0xbbb", LogIndex: 0}, + {ContractAddress: contractAddress, BlockchainID: blockchainID, Name: "E3", BlockNumber: 150, TransactionHash: "0xccc", LogIndex: 0}, + } + for _, ev := range events { + require.NoError(t, store.StoreContractEvent(ev)) + } + + block, err := store.GetLatestContractEventBlockNumber(contractAddress, blockchainID) + require.NoError(t, err) + assert.Equal(t, uint64(200), block) + }) + + t.Run("different contract returns zero", func(t *testing.T) { + block, err := store.GetLatestContractEventBlockNumber("0x9999999999999999999999999999999999999999", blockchainID) + require.NoError(t, err) + assert.Equal(t, uint64(0), block) + }) + + t.Run("different blockchain returns zero", func(t *testing.T) { + block, err := store.GetLatestContractEventBlockNumber(contractAddress, 999) + require.NoError(t, err) + assert.Equal(t, uint64(0), block) + }) +} + +func TestIsContractEventPresent(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + // Store a known event + ev := core.BlockchainEvent{ + ContractAddress: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + BlockchainID: 1, + Name: "TestEvent", + BlockNumber: 500, + TransactionHash: "0xAbCdEf1234567890AbCdEf1234567890AbCdEf1234567890AbCdEf1234567890", + LogIndex: 3, + } + require.NoError(t, store.StoreContractEvent(ev)) + + t.Run("existing event returns true", func(t *testing.T) { + present, err := store.IsContractEventPresent(1, 500, ev.TransactionHash, 3) + require.NoError(t, err) + assert.True(t, present) + }) + + t.Run("case-insensitive txHash match", func(t *testing.T) { + // Query with uppercase — stored value was lowercased by StoreContractEvent + present, err := store.IsContractEventPresent(1, 500, "0xABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890", 3) + require.NoError(t, err) + assert.True(t, present) + }) + + t.Run("wrong block number returns false", func(t *testing.T) { + present, err := store.IsContractEventPresent(1, 501, ev.TransactionHash, 3) + require.NoError(t, err) + assert.False(t, present) + }) + + t.Run("wrong log index returns false", func(t *testing.T) { + present, err := store.IsContractEventPresent(1, 500, ev.TransactionHash, 4) + require.NoError(t, err) + assert.False(t, present) + }) + + t.Run("wrong blockchain returns false", func(t *testing.T) { + present, err := store.IsContractEventPresent(2, 500, ev.TransactionHash, 3) + require.NoError(t, err) + assert.False(t, present) + }) + + t.Run("wrong txHash returns false", func(t *testing.T) { + present, err := store.IsContractEventPresent(1, 500, "0x0000000000000000000000000000000000000000000000000000000000000000", 3) + require.NoError(t, err) + assert.False(t, present) + }) +} diff --git a/nitronode/store/database/current_session_key_state.go b/nitronode/store/database/current_session_key_state.go new file mode 100644 index 000000000..4e4dcb166 --- /dev/null +++ b/nitronode/store/database/current_session_key_state.go @@ -0,0 +1,221 @@ +package database + +import ( + "errors" + "fmt" + "math" + "strings" + "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +// ErrSessionKeyNotAllowed is returned by LockSessionKeyState when the session key for the +// requested kind is bound to a wallet other than the submitter. The message is intentionally +// generic so the API does not confirm whether a given session_key is registered elsewhere. +var ErrSessionKeyNotAllowed = errors.New("session key not allowed") + +// SessionKeyKind discriminates the two session-key flavors stored in +// current_session_key_states_v1. Stored as SMALLINT in the DB. +type SessionKeyKind uint8 + +const ( + SessionKeyKindChannel SessionKeyKind = 1 + SessionKeyKindAppSession SessionKeyKind = 2 +) + +// CurrentSessionKeyStateV1 is the latest-version pointer per (user_address, session_key, kind). +// Reads of get_last_key_states JOIN this table to the corresponding history table +// (channel_session_key_states_v1 or app_session_key_states_v1) on +// (user_address, session_key, version), bounding per-request DB work to O(distinct keys). +// +// The uniqueIndex on (session_key, kind) mirrors the postgres constraint added by +// 20260508000000_session_key_ownership_constraints.sql so AutoMigrate (sqlite) enforces the +// same one-owner-per-key invariant that LockSessionKeyState relies on. The index name +// matches the postgres constraint name so both paths converge on a single source of truth. +type CurrentSessionKeyStateV1 struct { + UserAddress string `gorm:"column:user_address;primaryKey;size:42"` + SessionKey string `gorm:"column:session_key;primaryKey;size:42;uniqueIndex:current_session_key_states_v1_key_kind_uniq,priority:1"` + Kind SessionKeyKind `gorm:"column:kind;primaryKey;type:smallint;uniqueIndex:current_session_key_states_v1_key_kind_uniq,priority:2"` + Version uint64 `gorm:"column:version;not null"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (CurrentSessionKeyStateV1) TableName() string { + return "current_session_key_states_v1" +} + +// upsertCurrentSessionKeyState writes the latest version for (user_address, session_key, kind). +// EXCLUDED.version > version guard prevents an out-of-order writer from regressing the pointer. +func upsertCurrentSessionKeyState(tx *gorm.DB, userAddress, sessionKey string, kind SessionKeyKind, version uint64) error { + row := CurrentSessionKeyStateV1{ + UserAddress: strings.ToLower(userAddress), + SessionKey: strings.ToLower(sessionKey), + Kind: kind, + Version: version, + UpdatedAt: time.Now().UTC(), + } + + res := tx.Clauses(clause.OnConflict{ + Columns: []clause.Column{ + {Name: "user_address"}, + {Name: "session_key"}, + {Name: "kind"}, + }, + DoUpdates: clause.Assignments(map[string]interface{}{ + "version": gorm.Expr("EXCLUDED.version"), + "updated_at": gorm.Expr("EXCLUDED.updated_at"), + }), + Where: clause.Where{Exprs: []clause.Expression{ + gorm.Expr("EXCLUDED.version > current_session_key_states_v1.version"), + }}, + }).Create(&row) + + if err := res.Error; err != nil { + return fmt.Errorf("failed to upsert current session key state: %w", err) + } + return nil +} + +// LockSessionKeyState seeds the pointer row for (userAddress, session_key, kind) if absent +// and locks the (session_key, kind) row for the surrounding transaction. Returns the latest +// stored version for the caller's row, or ErrSessionKeyNotAllowed if the key is bound to a +// different wallet for this kind. +// +// The (session_key, kind) unique constraint guarantees there is at most one pointer row per +// (session_key, kind), so the SELECT ... FOR UPDATE that follows the no-op-on-conflict insert +// always converges on the same physical row regardless of who tried to seed first. A foreign +// wallet that races a legitimate owner ends up reading the legitimate owner back from the +// locked row and is rejected here, without parsing constraint-violation errors at write time. +// +// SELECT ... FOR UPDATE is postgres-only; on sqlite the locking clause is skipped and the +// surrounding transaction provides the necessary ordering for the in-process test setup. +// +// Seed-row permanence: the version=0 row written below is intentionally never deleted on +// failure paths (sig validation, version mismatch, cap exceeded, mid-tx errors). Once a wallet +// has staked a claim on (session_key, kind), no other wallet can take it for that kind — the +// seed is the ownership reservation, not a transient placeholder. CountSessionKeysForUser +// excludes version=0 rows so the per-user cap is unaffected, but the (session_key, kind) +// ownership bind is permanent by design. +// +// When locked.Version > 0, the matching history row's expires_at is also returned so callers +// can distinguish a reactivation (prev inactive → submitted active) from a rotation/update +// (prev still active) and re-run the per-user cap on the reactivation path. When +// locked.Version == 0 there is no history yet, so a zero time.Time is returned. +func (s *DBStore) LockSessionKeyState(userAddress, sessionKey string, kind SessionKeyKind) (uint64, time.Time, error) { + userAddress = strings.ToLower(userAddress) + sessionKey = strings.ToLower(sessionKey) + + seed := CurrentSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKey, + Kind: kind, + Version: 0, + UpdatedAt: time.Now().UTC(), + } + if err := s.db.Clauses(clause.OnConflict{DoNothing: true}).Create(&seed).Error; err != nil { + return 0, time.Time{}, fmt.Errorf("failed to ensure current session key state row exists: %w", err) + } + + query := s.db.Where("session_key = ? AND kind = ?", sessionKey, kind) + if s.db.Dialector.Name() == "postgres" { + query = query.Clauses(clause.Locking{Strength: "UPDATE"}) + } + + var locked CurrentSessionKeyStateV1 + err := query.First(&locked).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + // Must not happen: the seed insert above either created our row or no-op'd on + // an existing one, so a SELECT keyed on (session_key, kind) must hit a row. + // Treat as a hard error rather than falling through as unowned — silently + // returning version 0 here would let a submit bypass ownership enforcement. + return 0, time.Time{}, fmt.Errorf("session key pointer row missing after seed insert for (session_key=%s, kind=%d)", sessionKey, kind) + } + return 0, time.Time{}, fmt.Errorf("failed to lock current session key state: %w", err) + } + + if !strings.EqualFold(locked.UserAddress, userAddress) { + return 0, time.Time{}, ErrSessionKeyNotAllowed + } + + if locked.Version == 0 { + return 0, time.Time{}, nil + } + + expiresAt, err := s.fetchLatestSessionKeyExpiresAt(userAddress, sessionKey, locked.Version, kind) + if err != nil { + return 0, time.Time{}, err + } + return locked.Version, expiresAt, nil +} + +// fetchLatestSessionKeyExpiresAt returns the expires_at of the history row at +// (user_address, session_key, version) for the given kind. The pointer table guarantees +// at most one such row per (user, key, kind, version) so a missing history row is a hard +// inconsistency and surfaces as an error rather than a zero expiry. +func (s *DBStore) fetchLatestSessionKeyExpiresAt(userAddress, sessionKey string, version uint64, kind SessionKeyKind) (time.Time, error) { + type expiryRow struct { + ExpiresAt time.Time `gorm:"column:expires_at"` + } + + var table string + switch kind { + case SessionKeyKindAppSession: + table = "app_session_key_states_v1" + case SessionKeyKindChannel: + table = "channel_session_key_states_v1" + default: + return time.Time{}, fmt.Errorf("unknown session key kind: %d", kind) + } + + var row expiryRow + err := s.db.Table(table). + Select("expires_at"). + Where("user_address = ? AND session_key = ? AND version = ?", userAddress, sessionKey, version). + Take(&row).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return time.Time{}, fmt.Errorf("session key history row missing for (user=%s, session_key=%s, version=%d, kind=%d)", userAddress, sessionKey, version, kind) + } + return time.Time{}, fmt.Errorf("failed to load latest session key expires_at: %w", err) + } + return row.ExpiresAt, nil +} + +// CountSessionKeysForUser returns the number of distinct active session keys recorded for the +// wallet in the pointer table, across both kinds. Drives the per-user cap at submit time. +// Rows seeded by LockSessionKeyState (version=0) are excluded so that a failed-cap rejection +// does not itself leave a phantom row counted toward the cap. Revoked or naturally expired +// keys (expires_at <= now in the underlying history row) are also excluded so that a revoke +// frees the slot. A single now is bound for both kind branches so the count is internally +// consistent. +func (s *DBStore) CountSessionKeysForUser(userAddress string) (uint32, error) { + userAddress = strings.ToLower(userAddress) + now := time.Now().UTC() + + var channelCount int64 + err := s.db.Table("current_session_key_states_v1 AS c"). + Joins("JOIN channel_session_key_states_v1 h ON h.user_address = c.user_address AND h.session_key = c.session_key AND h.version = c.version"). + Where("c.user_address = ? AND c.kind = ? AND c.version > 0 AND h.expires_at > ?", userAddress, SessionKeyKindChannel, now). + Count(&channelCount).Error + if err != nil { + return 0, fmt.Errorf("failed to count channel session keys for user: %w", err) + } + + var appCount int64 + err = s.db.Table("current_session_key_states_v1 AS c"). + Joins("JOIN app_session_key_states_v1 h ON h.user_address = c.user_address AND h.session_key = c.session_key AND h.version = c.version"). + Where("c.user_address = ? AND c.kind = ? AND c.version > 0 AND h.expires_at > ?", userAddress, SessionKeyKindAppSession, now). + Count(&appCount).Error + if err != nil { + return 0, fmt.Errorf("failed to count app session keys for user: %w", err) + } + + total := channelCount + appCount + if total < 0 || total > math.MaxUint32 { + return 0, fmt.Errorf("session key count %d out of uint32 range", total) + } + return uint32(total), nil +} diff --git a/nitronode/store/database/current_session_key_state_test.go b/nitronode/store/database/current_session_key_state_test.go new file mode 100644 index 000000000..e667a77ab --- /dev/null +++ b/nitronode/store/database/current_session_key_state_test.go @@ -0,0 +1,396 @@ +package database + +import ( + "errors" + "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 TestCurrentSessionKeyStateV1_TableName(t *testing.T) { + assert.Equal(t, "current_session_key_states_v1", CurrentSessionKeyStateV1{}.TableName()) +} + +// TestCurrentSessionKeyStateV1_UniqueKeyKindConstraint pins the (session_key, kind) uniqueness +// invariant at the database layer on every supported dialect. Postgres gets it from migration +// 20260508000000; sqlite gets it from the uniqueIndex gorm tag via AutoMigrate. Without the +// tag, sqlite would silently accept two pointer rows for the same key/kind under different +// wallets, breaking LockSessionKeyState's read-first-then-check ownership flow. +func TestCurrentSessionKeyStateV1_UniqueKeyKindConstraint(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + now := time.Now().UTC() + first := CurrentSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Kind: SessionKeyKindAppSession, + Version: 1, + UpdatedAt: now, + } + require.NoError(t, db.Create(&first).Error) + + // Foreign wallet attempting the same (session_key, kind) must be rejected at the + // database layer, not just by application logic. + collision := CurrentSessionKeyStateV1{ + UserAddress: testUser2, + SessionKey: testSessionKey, + Kind: SessionKeyKindAppSession, + Version: 1, + UpdatedAt: now, + } + err := db.Create(&collision).Error + require.Error(t, err) + + // Same (session_key) under a different kind is allowed — the constraint is composite. + otherKind := CurrentSessionKeyStateV1{ + UserAddress: testUser2, + SessionKey: testSessionKey, + Kind: SessionKeyKindChannel, + Version: 1, + UpdatedAt: now, + } + require.NoError(t, db.Create(&otherKind).Error) +} + +func TestDBStore_LockSessionKeyState(t *testing.T) { + t.Run("Seeds row at version=0 on first call", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + v, expiresAt, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + assert.Equal(t, uint64(0), v) + assert.True(t, expiresAt.IsZero(), "expires_at must be zero when no history row exists") + + // Second call returns the same seeded row. + v2, expiresAt2, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + assert.Equal(t, uint64(0), v2) + assert.True(t, expiresAt2.IsZero()) + }) + + t.Run("Returns latest version and expires_at after a successful submit", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + futureExpiry := time.Now().Add(24 * time.Hour).UTC().Truncate(time.Second) + state := app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 1, + ExpiresAt: futureExpiry, + UserSig: "0xsig", + } + require.NoError(t, store.StoreAppSessionKeyState(state)) + + v, expiresAt, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + assert.Equal(t, uint64(1), v) + assert.WithinDuration(t, futureExpiry, expiresAt, time.Second) + }) + + t.Run("Returns past expires_at for a revoked latest version", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + // Active v1 followed by a revoke at v2 with past expires_at. + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig1", + })) + pastExpiry := time.Now().Add(-time.Hour).UTC().Truncate(time.Second) + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 2, + ExpiresAt: pastExpiry, + UserSig: "0xsig2", + })) + + v, expiresAt, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + assert.Equal(t, uint64(2), v) + assert.WithinDuration(t, pastExpiry, expiresAt, time.Second) + assert.True(t, expiresAt.Before(time.Now()), "revoked latest must surface a past expires_at") + }) + + t.Run("Channel and app_session kinds are independent", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + // Submit channel session key v1. + require.NoError(t, store.StoreChannelSessionKeyState(core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + + channelV, channelExpiresAt, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindChannel) + require.NoError(t, err) + assert.Equal(t, uint64(1), channelV) + assert.False(t, channelExpiresAt.IsZero()) + + // App-session pointer for the same (user, session_key) is unaffected. + appV, appExpiresAt, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + assert.Equal(t, uint64(0), appV) + assert.True(t, appExpiresAt.IsZero()) + }) + + t.Run("Foreign wallet trying to claim an already-owned (session_key, kind) is rejected", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + // User1 owns the session key for the app-session kind. + _, _, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + + // User2 attempts to lock the same (session_key, kind) — must surface the generic + // not-allowed sentinel without leaking that the key belongs to someone else. + _, _, err = store.LockSessionKeyState(testUser2, testSessionKey, SessionKeyKindAppSession) + require.Error(t, err) + assert.True(t, errors.Is(err, ErrSessionKeyNotAllowed)) + }) + + t.Run("Same (user, session_key) across both kinds is allowed", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + _, _, err := store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindChannel) + require.NoError(t, err) + _, _, err = store.LockSessionKeyState(testUser1, testSessionKey, SessionKeyKindAppSession) + require.NoError(t, err) + }) + + t.Run("Lowercases user_address and session_key", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + _, _, err := store.LockSessionKeyState( + "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + SessionKeyKindAppSession, + ) + require.NoError(t, err) + + // Lower-case query returns the same row (no duplicate seeded). + v, expiresAt, err := store.LockSessionKeyState( + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + SessionKeyKindAppSession, + ) + require.NoError(t, err) + assert.Equal(t, uint64(0), v) + assert.True(t, expiresAt.IsZero()) + + count, err := store.CountSessionKeysForUser("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + require.NoError(t, err) + // Seeded-only rows (version=0) must not count toward the cap. + assert.Equal(t, uint32(0), count) + }) +} + +func TestDBStore_CountSessionKeysForUser(t *testing.T) { + t.Run("Counts only rows with version > 0", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + // Seed-only row (Lock with no submit) must not be counted. + _, _, err := store.LockSessionKeyState(testUser1, testKeyA, SessionKeyKindAppSession) + require.NoError(t, err) + + count, err := store.CountSessionKeysForUser(testUser1) + require.NoError(t, err) + assert.Equal(t, uint32(0), count) + + // Real submit -> counted. + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + count, err = store.CountSessionKeysForUser(testUser1) + require.NoError(t, err) + assert.Equal(t, uint32(1), count) + }) + + t.Run("Counts across both kinds for the same user", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + require.NoError(t, store.StoreChannelSessionKeyState(core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyB, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + + count, err := store.CountSessionKeysForUser(testUser1) + require.NoError(t, err) + assert.Equal(t, uint32(2), count) + }) + + t.Run("Does not count keys belonging to a different user", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + + count, err := store.CountSessionKeysForUser(testUser2) + require.NoError(t, err) + assert.Equal(t, uint32(0), count) + }) + + t.Run("Revoked or expired keys do not count against the cap", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + // Active app-session key. + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + + // Revoked app-session key (submit at the next version with expires_at in the past). + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyB, + Version: 1, + ExpiresAt: time.Now().Add(-time.Hour), + UserSig: "0xsig", + })) + + // Active channel key for the same user. + require.NoError(t, store.StoreChannelSessionKeyState(core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig", + })) + + // Revoked channel key. + require.NoError(t, store.StoreChannelSessionKeyState(core.ChannelSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyB, + Version: 1, + ExpiresAt: time.Now().Add(-time.Hour), + UserSig: "0xsig", + })) + + count, err := store.CountSessionKeysForUser(testUser1) + require.NoError(t, err) + assert.Equal(t, uint32(2), count, "only active keys (1 app + 1 channel) should count") + }) + + t.Run("Rotating an existing key out via past expires_at frees the slot", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig_active", + })) + + count, err := store.CountSessionKeysForUser(testUser1) + require.NoError(t, err) + assert.Equal(t, uint32(1), count) + + // Submit version 2 with past expires_at as a revoke. + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testKeyA, + Version: 2, + ExpiresAt: time.Now().Add(-time.Hour), + UserSig: "0xsig_revoke", + })) + + count, err = store.CountSessionKeysForUser(testUser1) + require.NoError(t, err) + assert.Equal(t, uint32(0), count, "revoke must free the slot") + }) +} + +func TestDBStore_CurrentPointer_VersionMonotonic(t *testing.T) { + // Out-of-order writers must not regress the pointer (EXCLUDED.version > current.version). + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 1, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig1", + })) + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 3, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig3", + })) + + // Pointer reflects version 3. + v, err := store.GetLastAppSessionKeyVersion(testUser1, testSessionKey) + require.NoError(t, err) + assert.Equal(t, uint64(3), v) + + // A late-arriving v2 must not regress the pointer back to 2 (the EXCLUDED.version > current + // guard is what protects this; the history row insert itself is allowed). + require.NoError(t, store.StoreAppSessionKeyState(app.AppSessionKeyStateV1{ + UserAddress: testUser1, + SessionKey: testSessionKey, + Version: 2, + ExpiresAt: time.Now().Add(24 * time.Hour), + UserSig: "0xsig2", + })) + v, err = store.GetLastAppSessionKeyVersion(testUser1, testSessionKey) + require.NoError(t, err) + assert.Equal(t, uint64(3), v) +} diff --git a/clearnode/store/database/database.go b/nitronode/store/database/database.go similarity index 60% rename from clearnode/store/database/database.go rename to nitronode/store/database/database.go index bb01c3189..87796ed8e 100644 --- a/clearnode/store/database/database.go +++ b/nitronode/store/database/database.go @@ -1,9 +1,11 @@ package database import ( + "database/sql" "embed" "fmt" "log" + "strings" "time" "github.com/jmoiron/sqlx" @@ -18,23 +20,27 @@ import ( // In order to connect to Postgresql you need to fill out all the fields. // // To connect to sqlite, you just need to specify "sqlite" driver. -// By default it will use in-memory database. You can provide CLEARNODE_DATABASE_NAME to use the file. +// By default it will use in-memory database. You can provide NITRONODE_DATABASE_NAME to use the file. +// +// For Postgresql, NITRONODE_DATABASE_URL takes precedence: when set, it is used verbatim +// and the individual Username/Password/Host/Port/Name/SSLMode fields are ignored. type DatabaseConfig struct { - URL string `env:"CLEARNODE_DATABASE_URL" env-default:""` - Name string `env:"CLEARNODE_DATABASE_NAME" env-default:""` - Schema string `env:"CLEARNODE_DATABASE_SCHEMA" env-default:""` - Driver string `env:"CLEARNODE_DATABASE_DRIVER" env-default:"postgres"` - Username string `env:"CLEARNODE_DATABASE_USERNAME" env-default:"postgres"` - Password string `env:"CLEARNODE_DATABASE_PASSWORD" env-default:"your-super-secret-and-long-postgres-password"` - Host string `env:"CLEARNODE_DATABASE_HOST" env-default:"localhost"` - Port string `env:"CLEARNODE_DATABASE_PORT" env-default:"5432"` - Retries int `env:"CLEARNODE_DATABASE_RETRIES" env-default:"5"` + URL string `env:"NITRONODE_DATABASE_URL" env-default:""` + Name string `env:"NITRONODE_DATABASE_NAME" env-default:""` + Schema string `env:"NITRONODE_DATABASE_SCHEMA" env-default:""` + Driver string `env:"NITRONODE_DATABASE_DRIVER" env-default:"postgres"` + Username string `env:"NITRONODE_DATABASE_USERNAME" env-default:"postgres"` + Password string `env:"NITRONODE_DATABASE_PASSWORD" env-default:"your-super-secret-and-long-postgres-password"` + Host string `env:"NITRONODE_DATABASE_HOST" env-default:"localhost"` + Port string `env:"NITRONODE_DATABASE_PORT" env-default:"5432"` + SSLMode string `env:"NITRONODE_DATABASE_SSLMODE" env-default:"require"` + Retries int `env:"NITRONODE_DATABASE_RETRIES" env-default:"5"` // Connection pool settings - MaxOpenConns int `env:"CLEARNODE_DATABASE_MAX_OPEN_CONNS" env-default:"100"` - MaxIdleConns int `env:"CLEARNODE_DATABASE_MAX_IDLE_CONNS" env-default:"25"` - ConnMaxLifetime int `env:"CLEARNODE_DATABASE_CONN_MAX_LIFETIME_SEC" env-default:"300"` - ConnMaxIdleTime int `env:"CLEARNODE_DATABASE_CONN_MAX_IDLE_TIME_SEC" env-default:"60"` + MaxOpenConns int `env:"NITRONODE_DATABASE_MAX_OPEN_CONNS" env-default:"100"` + MaxIdleConns int `env:"NITRONODE_DATABASE_MAX_IDLE_CONNS" env-default:"25"` + ConnMaxLifetime int `env:"NITRONODE_DATABASE_CONN_MAX_LIFETIME_SEC" env-default:"300"` + ConnMaxIdleTime int `env:"NITRONODE_DATABASE_CONN_MAX_IDLE_TIME_SEC" env-default:"60"` } func ConnectToDB(cnf DatabaseConfig, embedMigrations embed.FS) (*gorm.DB, error) { @@ -120,19 +126,46 @@ func connectToSqlite(cnf DatabaseConfig) (*gorm.DB, error) { } // Migrate sqlite - migrateSqlite(db) + if err := migrateSqlite(db); err != nil { + return nil, fmt.Errorf("failed to auto-migrate sqlite: %w", err) + } log.Println("Successfully auto-migrated") return db, nil } +// validPostgresSSLModes lists sslmode values accepted by libpq / pgx. +// See https://www.postgresql.org/docs/current/libpq-ssl.html. +var validPostgresSSLModes = map[string]struct{}{ + "disable": {}, + "allow": {}, + "prefer": {}, + "require": {}, + "verify-ca": {}, + "verify-full": {}, +} + func postgresqlDbUrl(cnf DatabaseConfig) (string, error) { switch cnf.Driver { case "postgres": + // URL, when supplied, is used verbatim. The operator owns sslmode, search_path, + // and any other parameters encoded in it. + if cnf.URL != "" { + return cnf.URL, nil + } + + sslMode := cnf.SSLMode + if sslMode == "" { + sslMode = "require" + } + if _, ok := validPostgresSSLModes[sslMode]; !ok { + return "", fmt.Errorf("invalid sslmode %q: must be one of disable, allow, prefer, require, verify-ca, verify-full", sslMode) + } + dsn := fmt.Sprintf( - "user=%s password=%s host=%s port=%s dbname=%s sslmode=disable", - cnf.Username, cnf.Password, cnf.Host, cnf.Port, cnf.Name, + "user=%s password=%s host=%s port=%s dbname=%s sslmode=%s", + cnf.Username, cnf.Password, cnf.Host, cnf.Port, cnf.Name, sslMode, ) if cnf.Schema != "" { @@ -165,17 +198,15 @@ func ensurePostgresqlSchema(cnf DatabaseConfig) error { return err } - queryDbCheck := fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name='%s'", cnf.Schema) - if res, err := db.Exec(queryDbCheck); err != nil { + var exists int + if err := db.QueryRow("SELECT 1 FROM information_schema.schemata WHERE schema_name=$1", cnf.Schema).Scan(&exists); err != nil && err != sql.ErrNoRows { return fmt.Errorf("error while checking schema existance: %s", err.Error()) - } else if rows, err := res.RowsAffected(); err != nil { - return fmt.Errorf("error while checking schema existance: %s", err.Error()) - } else if rows > 0 { + } else if err == nil { log.Printf("Schema already exists: %s\n", cnf.Schema) return nil } - if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", cnf.Schema)); err != nil { + if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", quotePostgresIdentifier(cnf.Schema))); err != nil { return fmt.Errorf("error while creating schema: %s", err.Error()) } @@ -197,7 +228,7 @@ func migratePostgres(cnf DatabaseConfig, embedMigrations embed.FS) error { if cnf.Schema != "" { switch cnf.Driver { case "postgres": - if _, err := db.Exec(fmt.Sprintf("SET search_path TO %s", cnf.Schema)); err != nil { + if _, err := db.Exec(fmt.Sprintf("SET search_path TO %s", quotePostgresIdentifier(cnf.Schema))); err != nil { return fmt.Errorf("failed to set search path: %v", err) } } @@ -206,7 +237,7 @@ func migratePostgres(cnf DatabaseConfig, embedMigrations embed.FS) error { log.Println("Applying database migrations") goose.SetBaseFS(embedMigrations) if err := goose.Up(db, "config/migrations/"+cnf.Driver); err != nil { - panic(err) + return fmt.Errorf("goose migration failed: %w", err) } log.Println("Applied migrations") @@ -214,8 +245,32 @@ func migratePostgres(cnf DatabaseConfig, embedMigrations embed.FS) error { } func migrateSqlite(db *gorm.DB) error { - if err := db.AutoMigrate(&AppV1{}, &AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}); err != nil { + if err := db.AutoMigrate( + &AppV1{}, + &AppLedgerEntryV1{}, + &Channel{}, + &AppSessionV1{}, + &AppParticipantV1{}, + &ContractEvent{}, + &State{}, + &Transaction{}, + &BlockchainAction{}, + &AppSessionKeyStateV1{}, + &AppSessionKeyApplicationV1{}, + &AppSessionKeyAppSessionIDV1{}, + &ChannelSessionKeyStateV1{}, + &ChannelSessionKeyAssetV1{}, + &CurrentSessionKeyStateV1{}, + &UserBalance{}, + &UserStakedV1{}, + &ActionLogEntryV1{}, + &LifespanMetric{}, + ); err != nil { return err } return nil } + +func quotePostgresIdentifier(identifier string) string { + return `"` + strings.ReplaceAll(identifier, `"`, `""`) + `"` +} diff --git a/nitronode/store/database/database_test.go b/nitronode/store/database/database_test.go new file mode 100644 index 000000000..5cdc5c784 --- /dev/null +++ b/nitronode/store/database/database_test.go @@ -0,0 +1,77 @@ +package database + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPostgresqlDbUrl(t *testing.T) { + base := DatabaseConfig{ + Driver: "postgres", + Username: "user", + Password: "pass", + Host: "db.example.com", + Port: "5432", + Name: "nitronode", + } + + t.Run("DefaultsToRequireWhenSSLModeEmpty", func(t *testing.T) { + dsn, err := postgresqlDbUrl(base) + require.NoError(t, err) + assert.Contains(t, dsn, "sslmode=require") + assert.NotContains(t, dsn, "sslmode=disable") + }) + + t.Run("HonorsExplicitSSLMode", func(t *testing.T) { + cnf := base + cnf.SSLMode = "verify-full" + dsn, err := postgresqlDbUrl(cnf) + require.NoError(t, err) + assert.Contains(t, dsn, "sslmode=verify-full") + }) + + t.Run("AllowsDisableForLocalDev", func(t *testing.T) { + cnf := base + cnf.SSLMode = "disable" + dsn, err := postgresqlDbUrl(cnf) + require.NoError(t, err) + assert.Contains(t, dsn, "sslmode=disable") + }) + + t.Run("RejectsInvalidSSLMode", func(t *testing.T) { + cnf := base + cnf.SSLMode = "totally-bogus" + _, err := postgresqlDbUrl(cnf) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid sslmode") + }) + + t.Run("AppendsSearchPathWhenSchemaSet", func(t *testing.T) { + cnf := base + cnf.Schema = "tenant_a" + dsn, err := postgresqlDbUrl(cnf) + require.NoError(t, err) + assert.Contains(t, dsn, "search_path=tenant_a") + }) + + t.Run("URLOverridesIndividualFields", func(t *testing.T) { + cnf := base + cnf.URL = "postgres://override:secret@otherhost:6543/otherdb?sslmode=verify-ca" + cnf.SSLMode = "disable" // ignored when URL set + dsn, err := postgresqlDbUrl(cnf) + require.NoError(t, err) + assert.Equal(t, cnf.URL, dsn) + assert.False(t, strings.Contains(dsn, "user=user"), "URL must be returned verbatim") + }) + + t.Run("RejectsUnsupportedDriver", func(t *testing.T) { + cnf := base + cnf.Driver = "mysql" + _, err := postgresqlDbUrl(cnf) + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported driver") + }) +} diff --git a/clearnode/store/database/db_store.go b/nitronode/store/database/db_store.go similarity index 50% rename from clearnode/store/database/db_store.go rename to nitronode/store/database/db_store.go index c87ae2331..a9f146795 100644 --- a/clearnode/store/database/db_store.go +++ b/nitronode/store/database/db_store.go @@ -49,12 +49,44 @@ func (s *DBStore) GetUserBalances(wallet string) ([]core.BalanceEntry, error) { result = append(result, core.BalanceEntry{ Asset: balance.Asset, Balance: balance.Balance, + Enforced: balance.Enforced, }) } return result, nil } +// RefreshUserEnforcedBalance recomputes the enforced balance from the user's open home channel on-chain state. +func (s *DBStore) RefreshUserEnforcedBalance(wallet, asset string) error { + wallet = strings.ToLower(wallet) + asset = strings.ToLower(asset) + + // The protocol enforces at most one open home channel per (user, asset), so + // the subquery matches a single row in practice. ORDER BY is added as + // defence-in-depth to keep the result deterministic if that invariant is + // ever violated. + err := s.db.Exec(` + UPDATE user_balances + SET enforced = COALESCE(( + SELECT s.home_user_balance + FROM channels c + JOIN channel_states s ON s.home_channel_id = c.channel_id AND s.version = c.state_version + WHERE c.user_wallet = user_balances.user_wallet + AND c.asset = user_balances.asset + AND c.type = 1 + AND c.status <= 1 + AND c.state_version > 0 + ORDER BY c.updated_at DESC + LIMIT 1 + ), 0) + WHERE user_wallet = ? AND asset = ? + `, wallet, asset).Error + if err != nil { + return fmt.Errorf("failed to refresh user locked balance: %w", err) + } + return nil +} + // LockUserState locks a user's balance row for update (postgres only, must be used within a transaction). // Uses INSERT ... ON CONFLICT DO NOTHING to ensure the row exists, then SELECT ... FOR UPDATE to lock it. // Returns the current balance or zero if the row was just inserted. @@ -167,41 +199,147 @@ func (s *DBStore) EnsureNoOngoingStateTransitions(wallet, asset string) error { switch result.TransitionType { case core.TransitionTypeHomeDeposit: // Verify last_state.version == home_channel.state_version - if result.HomeChannelVersion != nil && result.StateVersion != *result.HomeChannelVersion { + if result.HomeChannelVersion == nil || result.StateVersion != *result.HomeChannelVersion { return fmt.Errorf("home deposit is still ongoing") } case core.TransitionTypeHomeWithdrawal: // Verify last_state.version == home_channel.state_version - if result.HomeChannelVersion != nil && result.StateVersion != *result.HomeChannelVersion { + if result.HomeChannelVersion == nil || result.StateVersion != *result.HomeChannelVersion { return fmt.Errorf("home withdrawal is still ongoing") } case core.TransitionTypeMutualLock: // Verify last_state.version == home_channel.state_version == escrow_channel.state_version - if result.HomeChannelVersion != nil && result.StateVersion != *result.HomeChannelVersion || - result.EscrowChannelVersion != nil && result.StateVersion != *result.EscrowChannelVersion { + if result.HomeChannelVersion == nil || result.StateVersion != *result.HomeChannelVersion || + result.EscrowChannelVersion == nil || result.StateVersion != *result.EscrowChannelVersion { return fmt.Errorf("mutual lock is still ongoing") } case core.TransitionTypeEscrowLock: // Verify last_state.version == escrow_channel.state_version - if result.EscrowChannelVersion != nil && result.StateVersion != *result.EscrowChannelVersion { + if result.EscrowChannelVersion == nil || result.StateVersion != *result.EscrowChannelVersion { return fmt.Errorf("escrow lock is still ongoing") } case core.TransitionTypeEscrowWithdraw: // Verify last_state.version == escrow_channel.state_version - if result.EscrowChannelVersion != nil && result.StateVersion != *result.EscrowChannelVersion { + if result.EscrowChannelVersion == nil || result.StateVersion != *result.EscrowChannelVersion { return fmt.Errorf("escrow withdrawal is still ongoing") } case core.TransitionTypeMigrate: - // Verify last_state.version == home_channel.state_version - if result.HomeChannelVersion != nil && result.StateVersion != *result.HomeChannelVersion { + // NOTE: Migration flows are not yet active off-chain. This case is currently unreachable + // because submit_state.go rejects TransitionTypeMigrate before EnsureNoOngoingStateTransitions + // is called. When migration is implemented, this check must be extended: verifying that + // last_state.version == home_channel.state_version confirms only that the preparation state + // was enforced on the OLD home chain. A complete gate must also confirm that + // initiateMigration() was submitted on the NEW home chain (i.e. a MIGRATING_IN channel + // exists with a matching state version), matching the MigrationInInitiated lifecycle step + // documented in protocol-description.md. + if result.HomeChannelVersion == nil || result.StateVersion != *result.HomeChannelVersion { return fmt.Errorf("home chain migration is still ongoing") } } return nil } + +// EnsureNoOngoingEscrowOperation validates that the user has no in-flight escrow +// operation that would prevent the node from issuing receiver-side states (transfer +// receive, app-session release). +// +// Validation logic by latest signed transition type: +// - escrow_lock / mutual_lock: always considered ongoing (no finalization yet) +// - escrow_deposit: considered settled when the on-chain escrow channel version +// equals the signed state version (finalize landed). When the chain is exactly +// one behind, the signed N+1 finalize state lives off-chain and is gated on +// the escrow channel status: allowed for Open (protocol-intended steady state +// before the purge queue fires) and Closed (post-purge or post-finalize); +// blocked for Challenged (on-chain resolution still racing — finalize tx may +// not land, escrow chain may settle at INITIATE, and replaying N+1 later +// could violate engine invariants). +// - escrow_withdraw: considered ongoing while the on-chain escrow channel state +// version has not caught up with the signed state version +// - any other transition: not an escrow operation, allow +func (s *DBStore) EnsureNoOngoingEscrowOperation(wallet, asset string) error { + wallet = strings.ToLower(wallet) + + type escrowCheck struct { + TransitionType core.TransitionType + StateVersion uint64 + EscrowChannelVersion *uint64 + EscrowChannelStatus *core.ChannelStatus + } + + var result escrowCheck + tx := s.db.Raw(` + SELECT + s.transition_type as transition_type, + s.version as state_version, + ec.state_version as escrow_channel_version, + ec.status as escrow_channel_status + FROM channel_states s + LEFT JOIN channels ec ON ec.channel_id = s.escrow_channel_id + WHERE s.user_wallet = ? + AND s.asset = ? + AND s.user_sig IS NOT NULL + AND s.node_sig IS NOT NULL + ORDER BY s.epoch DESC, s.version DESC + LIMIT 1 + `, wallet, asset).Scan(&result) + + if tx.Error != nil { + return fmt.Errorf("failed to check ongoing escrow operation: %w", tx.Error) + } + if tx.RowsAffected == 0 { + return nil + } + + switch result.TransitionType { + case core.TransitionTypeEscrowLock: + return fmt.Errorf("escrow lock is still ongoing") + + case core.TransitionTypeMutualLock: + return fmt.Errorf("mutual lock is still ongoing") + + case core.TransitionTypeEscrowDeposit: + // Accept escrow channel at signed state version (finalize landed) or one + // behind (signed N+1 finalize sits off-chain; on-chain still at INITIATE + // version N). The one-behind branch is status-gated: Open is the protocol- + // intended steady state until the purge queue fires, Closed covers post- + // purge and post-finalize. Challenged is blocked because the on-chain + // resolution is still racing — finalize may not land in time, the escrow + // chain may settle at INITIATE, and replaying N+1 later could violate + // engine invariants. + // Compare via *v + 1 == StateVersion to avoid uint underflow when version is 0. + if result.EscrowChannelVersion == nil { + return fmt.Errorf("escrow deposit finalization is still ongoing") + } + onChain := *result.EscrowChannelVersion + signed := result.StateVersion + switch { + case onChain == signed: + // finalize already landed or signed state is older — allow + case onChain+1 == signed: + if result.EscrowChannelStatus == nil { + return fmt.Errorf("escrow deposit finalization is still ongoing") + } + switch *result.EscrowChannelStatus { + case core.ChannelStatusOpen, core.ChannelStatusClosed: + // allow + default: + return fmt.Errorf("escrow deposit finalization is still ongoing") + } + default: + return fmt.Errorf("escrow deposit finalization is still ongoing") + } + + case core.TransitionTypeEscrowWithdraw: + if result.EscrowChannelVersion == nil || result.StateVersion != *result.EscrowChannelVersion { + return fmt.Errorf("escrow withdrawal finalization is still ongoing") + } + } + + return nil +} diff --git a/nitronode/store/database/db_store_test.go b/nitronode/store/database/db_store_test.go new file mode 100644 index 000000000..72125e980 --- /dev/null +++ b/nitronode/store/database/db_store_test.go @@ -0,0 +1,1617 @@ +package database + +import ( + "testing" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDBStore_GetUserBalances(t *testing.T) { + t.Run("Success - Get balances for single asset", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + + // Create state with balance + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + // Lock user state before storing (ensures row exists in user_balances) + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + balances, err := store.GetUserBalances("0xuser123") + require.NoError(t, err) + + assert.Len(t, balances, 1) + assert.Equal(t, "USDC", balances[0].Asset) + assert.Equal(t, decimal.NewFromInt(1000), balances[0].Balance) + }) + + t.Run("Success - Get balances for multiple assets", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + + // Create state for USDC + state1 := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + // Create state for ETH + state2 := core.State{ + ID: "state2", + Asset: "ETH", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(5), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + // Lock user states before storing (ensures rows exist in user_balances) + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + _, err = store.LockUserState("0xuser123", "ETH") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) + + balances, err := store.GetUserBalances("0xuser123") + require.NoError(t, err) + + assert.Len(t, balances, 2) + + // Find balances by asset + var usdcBalance, ethBalance *core.BalanceEntry + for i := range balances { + switch balances[i].Asset { + case "USDC": + usdcBalance = &balances[i] + case "ETH": + ethBalance = &balances[i] + } + } + + require.NotNil(t, usdcBalance) + require.NotNil(t, ethBalance) + assert.Equal(t, decimal.NewFromInt(1000), usdcBalance.Balance) + assert.Equal(t, decimal.NewFromInt(5), ethBalance.Balance) + }) + + t.Run("Success - Returns latest version for each asset", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + + // Create multiple versions for same asset + state1 := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + state2 := core.State{ + ID: "state2", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 2, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(2000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + // Lock user state before storing (ensures row exists in user_balances) + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) + + balances, err := store.GetUserBalances("0xuser123") + require.NoError(t, err) + + assert.Len(t, balances, 1) + assert.Equal(t, "USDC", balances[0].Asset) + assert.Equal(t, decimal.NewFromInt(2000), balances[0].Balance) // Latest version + }) + + t.Run("Success - Returns latest epoch for each asset", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + + // Create states with different epochs + state1 := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 5, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + state2 := core.State{ + ID: "state2", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 2, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(3000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + + // Lock user state before storing (ensures row exists in user_balances) + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) + + balances, err := store.GetUserBalances("0xuser123") + require.NoError(t, err) + + assert.Len(t, balances, 1) + assert.Equal(t, decimal.NewFromInt(3000), balances[0].Balance) // Latest epoch + }) + + t.Run("No balances found", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + balances, err := store.GetUserBalances("0xnonexistent") + require.NoError(t, err) + assert.Empty(t, balances) + }) +} + +func TestDBStore_EnsureNoOngoingStateTransitions(t *testing.T) { + t.Run("No previous state - No error", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + err := store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.NoError(t, err) + }) + + t.Run("HomeDeposit - Versions match", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + userSig := "0xusersig" + nodeSig := "0xnodesig" + + // Create channel + channel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 1, // Matches state version + } + require.NoError(t, store.CreateChannel(channel)) + + // Create signed state + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeHomeDeposit, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + + // Lock user state before storing + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.NoError(t, err) + }) + + t.Run("HomeDeposit - Versions mismatch", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + userSig := "0xusersig" + nodeSig := "0xnodesig" + + // Create channel with different version + channel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 0, // Doesn't match state version + } + require.NoError(t, store.CreateChannel(channel)) + + // Create signed state + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeHomeDeposit, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + + // Lock user state before storing + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + assert.Error(t, err) + assert.Contains(t, err.Error(), "home deposit is still ongoing") + }) + + t.Run("MutualLock - Both versions match", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + escrowChannelID := "0xescrowchannel456" + userSig := "0xusersig" + nodeSig := "0xnodesig" + + // Create home channel + homeChannel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 2, // Matches state version + } + require.NoError(t, store.CreateChannel(homeChannel)) + + // Create escrow channel + escrowChannel := core.Channel{ + ChannelID: escrowChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeEscrow, + BlockchainID: 137, + TokenAddress: "0xtoken456", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 2, // Matches state version + } + require.NoError(t, store.CreateChannel(escrowChannel)) + + // Create signed state + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 2, + HomeChannelID: &homeChannelID, + EscrowChannelID: &escrowChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeMutualLock, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + EscrowLedger: &core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + + // Lock user state before storing + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.NoError(t, err) + }) + + t.Run("MutualLock - Home channel version mismatch", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + escrowChannelID := "0xescrowchannel456" + userSig := "0xusersig" + nodeSig := "0xnodesig" + + // Create home channel with mismatched version + homeChannel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 1, // Doesn't match state version + } + require.NoError(t, store.CreateChannel(homeChannel)) + + // Create escrow channel + escrowChannel := core.Channel{ + ChannelID: escrowChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeEscrow, + BlockchainID: 137, + TokenAddress: "0xtoken456", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 2, + } + require.NoError(t, store.CreateChannel(escrowChannel)) + + // Create signed state + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 2, + HomeChannelID: &homeChannelID, + EscrowChannelID: &escrowChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeMutualLock, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + EscrowLedger: &core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + + // Lock user state before storing + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + assert.Error(t, err) + assert.Contains(t, err.Error(), "mutual lock is still ongoing") + }) + + t.Run("EscrowWithdraw - Versions match", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + escrowChannelID := "0xescrowchannel456" + userSig := "0xusersig" + nodeSig := "0xnodesig" + + // Create escrow channel + escrowChannel := core.Channel{ + ChannelID: escrowChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeEscrow, + BlockchainID: 137, + TokenAddress: "0xtoken456", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 4, // Matches state version + } + require.NoError(t, store.CreateChannel(escrowChannel)) + + // Create signed state + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 4, + HomeChannelID: &homeChannelID, + EscrowChannelID: &escrowChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeEscrowWithdraw, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + EscrowLedger: &core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + + // Lock user state before storing + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.NoError(t, err) + }) + + t.Run("Unsigned state - No validation", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + + // Create channel with mismatched version + channel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 0, + } + require.NoError(t, store.CreateChannel(channel)) + + // Create unsigned state + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeHomeDeposit, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + // No signatures + } + + // Lock user state before storing + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(state, "")) + + // Should not error because there's no signed state + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.NoError(t, err) + }) + + const wallet = "0xuser123" + const asset = "USDC" + const homeChannelID = "0xhomechannel123" + const escrowChannelID = "0xescrowchannel456" + userSig := "0xusersig" + nodeSig := "0xnodesig" + + newHomeChannel := func(version uint64) core.Channel { + return core.Channel{ + ChannelID: homeChannelID, + UserWallet: wallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: version, + } + } + + newEscrowChannel := func(version uint64) core.Channel { + return core.Channel{ + ChannelID: escrowChannelID, + UserWallet: wallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + BlockchainID: 137, + TokenAddress: "0xtoken456", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: version, + } + } + + newSignedState := func(version uint64, transitionType core.TransitionType, withEscrow bool) core.State { + hc := homeChannelID + state := core.State{ + ID: "state1", + Asset: asset, + UserWallet: wallet, + Epoch: 1, + Version: version, + HomeChannelID: &hc, + Transition: core.Transition{Type: transitionType}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + if withEscrow { + ec := escrowChannelID + state.EscrowChannelID = &ec + state.EscrowLedger = &core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + } + } + return state + } + + storeState := func(t *testing.T, store DatabaseStore, state core.State) { + t.Helper() + _, err := store.LockUserState(wallet, asset) + require.NoError(t, err) + require.NoError(t, store.StoreUserState(state, "")) + } + + t.Run("HomeDeposit - home channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + storeState(t, store, newSignedState(1, core.TransitionTypeHomeDeposit, false)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "home deposit is still ongoing") + }) + + t.Run("HomeWithdrawal - home channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + storeState(t, store, newSignedState(1, core.TransitionTypeHomeWithdrawal, false)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "home withdrawal is still ongoing") + }) + + t.Run("MutualLock - home channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(newEscrowChannel(2))) + storeState(t, store, newSignedState(2, core.TransitionTypeMutualLock, true)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "mutual lock is still ongoing") + }) + + t.Run("MutualLock - escrow channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(newHomeChannel(2))) + storeState(t, store, newSignedState(2, core.TransitionTypeMutualLock, true)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "mutual lock is still ongoing") + }) + + t.Run("EscrowLock - escrow channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(newHomeChannel(1))) + storeState(t, store, newSignedState(1, core.TransitionTypeEscrowLock, true)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow lock is still ongoing") + }) + + t.Run("EscrowWithdraw - escrow channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(newHomeChannel(4))) + storeState(t, store, newSignedState(4, core.TransitionTypeEscrowWithdraw, true)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow withdrawal is still ongoing") + }) + + t.Run("Migrate - home channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + storeState(t, store, newSignedState(1, core.TransitionTypeMigrate, false)) + + err := store.EnsureNoOngoingStateTransitions(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "home chain migration is still ongoing") + }) +} + +func TestDBStore_UpdateStateSigsIfMissing(t *testing.T) { + t.Run("Backfills user_sig when null and unblocks gate", func(t *testing.T) { + // This is the wedge-recovery path: a node-only state was checkpointed on chain + // (e.g. recipient submitted a transfer_receive state directly). After the event + // reactor backfills user_sig, EnsureNoOngoingStateTransitions must see a fully + // signed row at the on-chain version and pass. + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + nodeSig := "0xnodesig" + + channel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 2, + } + require.NoError(t, store.CreateChannel(channel)) + + // Node-only state at version 2; gate would normally skip this row and find + // nothing else, returning nil. To exercise the wedge, also seed an older + // bilateral state at version 1. + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + bilateralUserSig := "0xprior" + bilateralNodeSig := "0xpriornode" + bilateral := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeHomeDeposit, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &bilateralUserSig, + NodeSig: &bilateralNodeSig, + } + require.NoError(t, store.StoreUserState(bilateral, "")) + + nodeOnly := core.State{ + ID: "state2", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 2, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeTransferReceive, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(750), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + NodeSig: &nodeSig, + } + require.NoError(t, store.StoreUserState(nodeOnly, "")) + + // Pre-backfill: gate sees bilateral row at version 1, channel.state_version is 2 → mismatch. + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.Error(t, err) + + // Backfill the user signature recovered from the on-chain event. + recoveredSig := "0xrecovered" + require.NoError(t, store.UpdateStateSigsIfMissing(homeChannelID, 2, recoveredSig, "")) + + got, err := store.GetStateByID("state2") + require.NoError(t, err) + require.NotNil(t, got) + require.NotNil(t, got.UserSig) + assert.Equal(t, recoveredSig, *got.UserSig) + + // Post-backfill: gate sees the now-bilateral row at version 2, matches channel state_version. + err = store.EnsureNoOngoingStateTransitions("0xuser123", "USDC") + require.NoError(t, err) + }) + + t.Run("Idempotent on replay - existing sig preserved", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + userSig := "0xexisting" + nodeSig := "0xnodesig" + + channel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + require.NoError(t, store.CreateChannel(channel)) + + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + + state := core.State{ + ID: "state1", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeHomeDeposit, + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1000), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + NodeSig: &nodeSig, + } + require.NoError(t, store.StoreUserState(state, "")) + + // Replayed event would carry a different (or any) sig; existing one must not be overwritten. + require.NoError(t, store.UpdateStateSigsIfMissing(homeChannelID, 1, "0xshould-not-overwrite", "0xshould-not-overwrite-node")) + + got, err := store.GetStateByID("state1") + require.NoError(t, err) + require.NotNil(t, got) + require.NotNil(t, got.UserSig) + assert.Equal(t, userSig, *got.UserSig) + }) + + t.Run("Empty sig is no-op", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + require.NoError(t, store.UpdateStateSigsIfMissing(homeChannelID, 1, "", "")) + }) + + t.Run("Unknown version returns no error", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + require.NoError(t, store.UpdateStateSigsIfMissing(homeChannelID, 99, "0xanything", "0xanything-node")) + }) + + t.Run("Backfills node_sig when user_sig already present", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + homeChannelID := "0xhomechannel123" + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: homeChannelID, + UserWallet: "0xuser123", + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 1, + })) + + _, err := store.LockUserState("0xuser123", "USDC") + require.NoError(t, err) + userSig := "0xusersigonly" + state := core.State{ + ID: "state-user-only", + Asset: "USDC", + UserWallet: "0xuser123", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{Type: core.TransitionTypeHomeDeposit}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(100), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: &userSig, + } + require.NoError(t, store.StoreUserState(state, "")) + + require.NoError(t, store.UpdateStateSigsIfMissing(homeChannelID, 1, "0xshould-not-overwrite-user", "0xrecoverednode")) + + got, err := store.GetStateByID("state-user-only") + require.NoError(t, err) + require.NotNil(t, got) + require.NotNil(t, got.UserSig) + assert.Equal(t, userSig, *got.UserSig, "existing user_sig must be preserved") + require.NotNil(t, got.NodeSig) + assert.Equal(t, "0xrecoverednode", *got.NodeSig) + }) +} + +func TestDBStore_SumNetTransitionAmountAfterVersion(t *testing.T) { + const wallet = "0xuser123" + const asset = "USDC" + const homeChannelID = "0xhomechannel123" + + setupChannel := func(t *testing.T, store DatabaseStore) { + t.Helper() + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: homeChannelID, + UserWallet: wallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusChallenged, + StateVersion: 5, + })) + } + + storeState := func(t *testing.T, store DatabaseStore, epoch, version uint64, transitionType core.TransitionType, amount decimal.Decimal, hasNodeSig bool) { + t.Helper() + channelIDLocal := homeChannelID + s := core.State{ + ID: core.GetStateID(wallet, asset, epoch, version), + Asset: asset, + UserWallet: wallet, + Epoch: epoch, + Version: version, + HomeChannelID: &channelIDLocal, + Transition: core.Transition{ + Type: transitionType, + TxID: "0xtx", + AccountID: "0xacct", + Amount: amount, + }, + HomeLedger: core.Ledger{ + UserBalance: amount, + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + userSig := "0xusersig" + s.UserSig = &userSig + if hasNodeSig { + nodeSig := "0xnodesig" + s.NodeSig = &nodeSig + } + _, err := store.LockUserState(wallet, asset) + require.NoError(t, err) + require.NoError(t, store.StoreUserState(s, "")) + } + + t.Run("Scenario 3 - dust during challenge (unsigned receive only)", func(t *testing.T) { + // Pure MF2-H01 path: attacker sends dust during challenge window, the row is + // stored unsigned per the suppression rule. Sum should equal the dust amount. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + storeState(t, store, 1, 2, core.TransitionTypeTransferReceive, decimal.NewFromFloat(0.001), false) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 1) + require.NoError(t, err) + assert.True(t, decimal.NewFromFloat(0.001).Equal(net), "want 0.001, got %s", net.String()) + }) + + t.Run("Scenario 2 - signed pre-challenge receives stranded by stale-state close", func(t *testing.T) { + // User went TS, TR, TS, TR while channel was Open (all rows node-signed), then + // challenged with the early TS state. Onchain payout reflects the early state; + // the offchain net change above closure (the two TR minus the second TS) is what + // the rescue must credit. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + // closureVersion = 2; v3 TR +1, v4 TS -1, v5 TR +1. + storeState(t, store, 1, 3, core.TransitionTypeTransferReceive, decimal.NewFromInt(1), true) + storeState(t, store, 1, 4, core.TransitionTypeTransferSend, decimal.NewFromInt(1), true) + storeState(t, store, 1, 5, core.TransitionTypeTransferReceive, decimal.NewFromInt(1), true) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 2) + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(1).Equal(net), "want 1, got %s", net.String()) + }) + + t.Run("Scenario 1 - HomeDeposit poison is excluded, real receive counted", func(t *testing.T) { + // Attacker tricks user into signing a fake HomeDeposit that has no onchain + // backing; user challenges with an earlier clean state. The poison row must not + // contribute to the rescue (otherwise the node pays out a phantom credit). The + // legitimate TR that landed above closure still does. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + // closureVersion = 2; v3 poisoned HomeDeposit +300000, v4 real TR +1. + storeState(t, store, 1, 3, core.TransitionTypeHomeDeposit, decimal.NewFromInt(300000), true) + storeState(t, store, 1, 4, core.TransitionTypeTransferReceive, decimal.NewFromInt(1), true) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 2) + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(1).Equal(net), "want 1, got %s", net.String()) + }) + + t.Run("Adversarial rollback produces negative net (caller clamps)", func(t *testing.T) { + // User picks a closure version where her own offchain balance was higher than + // the head — she's rolling back a big send to retain the funds onchain. Sum + // reports the true negative net; clamp is the caller's job. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + // closureVersion = 1; v2 TR +1, v3 TS -50. + storeState(t, store, 1, 2, core.TransitionTypeTransferReceive, decimal.NewFromInt(1), true) + storeState(t, store, 1, 3, core.TransitionTypeTransferSend, decimal.NewFromInt(50), true) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 1) + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(-49).Equal(net), "want -49, got %s", net.String()) + }) + + t.Run("Commit transitions deduct from net like sends", func(t *testing.T) { + // User committed funds to an app session above closure: the commit moves value + // from the home ledger to the session. If the session also released some of it + // back, release contributes positively. Both must net out. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + storeState(t, store, 1, 3, core.TransitionTypeTransferReceive, decimal.NewFromInt(5), true) + storeState(t, store, 1, 4, core.TransitionTypeCommit, decimal.NewFromInt(3), true) + storeState(t, store, 1, 5, core.TransitionTypeRelease, decimal.NewFromInt(1), false) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 2) + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(3).Equal(net), "want 3, got %s", net.String()) + }) + + t.Run("Excluded transition kinds contribute zero", func(t *testing.T) { + // HomeDeposit / HomeWithdrawal / EscrowDeposit / EscrowWithdraw / Migrate / + // Finalize / Acknowledgement either need onchain backing the chain didn't enforce + // or belong to a different ledger; none should affect the rescue sum. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + storeState(t, store, 1, 3, core.TransitionTypeHomeDeposit, decimal.NewFromInt(100), true) + storeState(t, store, 1, 4, core.TransitionTypeHomeWithdrawal, decimal.NewFromInt(50), true) + storeState(t, store, 1, 5, core.TransitionTypeEscrowDeposit, decimal.NewFromInt(10), true) + storeState(t, store, 1, 6, core.TransitionTypeEscrowWithdraw, decimal.NewFromInt(20), true) + storeState(t, store, 1, 7, core.TransitionTypeMigrate, decimal.NewFromInt(5), true) + storeState(t, store, 1, 8, core.TransitionTypeFinalize, decimal.NewFromInt(0), true) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 2) + require.NoError(t, err) + assert.True(t, net.IsZero(), "want 0, got %s", net.String()) + }) + + t.Run("Returns zero when no matches", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 0) + require.NoError(t, err) + assert.True(t, net.IsZero()) + }) + + t.Run("Strict > excludes rows at the closure version itself", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + // closureVersion = 6; a TR exactly at v6 must not contribute. + storeState(t, store, 1, 5, core.TransitionTypeTransferReceive, decimal.NewFromInt(99), true) + storeState(t, store, 1, 6, core.TransitionTypeTransferReceive, decimal.NewFromInt(99), true) + storeState(t, store, 1, 7, core.TransitionTypeTransferReceive, decimal.NewFromInt(3), false) + + net, err := store.SumNetTransitionAmountAfterVersion(homeChannelID, 6) + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(3).Equal(net), "want 3, got %s", net.String()) + }) +} + +func TestDBStore_HasSignedFinalize(t *testing.T) { + const wallet = "0xuser123" + const asset = "USDC" + const homeChannelID = "0xhomechannel123" + + setupChannel := func(t *testing.T, store DatabaseStore) { + t.Helper() + require.NoError(t, store.CreateChannel(core.Channel{ + ChannelID: homeChannelID, + UserWallet: wallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusChallenged, + StateVersion: 5, + })) + } + + storeState := func(t *testing.T, store DatabaseStore, version uint64, transitionType core.TransitionType, hasNodeSig bool) { + t.Helper() + channelIDLocal := homeChannelID + s := core.State{ + ID: core.GetStateID(wallet, asset, 1, version), + Asset: asset, + UserWallet: wallet, + Epoch: 1, + Version: version, + HomeChannelID: &channelIDLocal, + Transition: core.Transition{ + Type: transitionType, + TxID: "0xtx", + AccountID: "0xacct", + Amount: decimal.Zero, + }, + } + userSig := "0xusersig" + s.UserSig = &userSig + if hasNodeSig { + nodeSig := "0xnodesig" + s.NodeSig = &nodeSig + } + _, err := store.LockUserState(wallet, asset) + require.NoError(t, err) + require.NoError(t, store.StoreUserState(s, "")) + } + + t.Run("True when node-signed Finalize exists", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + storeState(t, store, 6, core.TransitionTypeTransferReceive, true) + storeState(t, store, 7, core.TransitionTypeFinalize, true) + + got, err := store.HasSignedFinalize(homeChannelID) + require.NoError(t, err) + assert.True(t, got) + }) + + t.Run("False when no Finalize state exists", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + storeState(t, store, 6, core.TransitionTypeTransferReceive, true) + storeState(t, store, 7, core.TransitionTypeTransferReceive, true) + + got, err := store.HasSignedFinalize(homeChannelID) + require.NoError(t, err) + assert.False(t, got) + }) + + t.Run("False when Finalize exists without node sig", func(t *testing.T) { + // Pins the documented invariant: a Finalize row whose node_sig is NULL must not + // be treated as node-signed, even though SubmitState today never stores one in + // that shape. Guards against a regression that drops the node_sig predicate or + // a future path that writes Finalize rows ahead of the node signature. + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + setupChannel(t, store) + + storeState(t, store, 7, core.TransitionTypeFinalize, false) + + got, err := store.HasSignedFinalize(homeChannelID) + require.NoError(t, err) + assert.False(t, got) + }) +} + +func TestDBStore_EnsureNoOngoingEscrowOperation(t *testing.T) { + const wallet = "0xuser123" + const asset = "USDC" + const homeChannelID = "0xhomechannel123" + const escrowChannelID = "0xescrowchannel456" + const userSig = "0xusersig" + const nodeSig = "0xnodesig" + + homeChannel := core.Channel{ + ChannelID: homeChannelID, + UserWallet: wallet, + Asset: "usdc", + Type: core.ChannelTypeHome, + BlockchainID: 1, + TokenAddress: "0xtoken123", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: 1, + } + + newEscrowChannel := func(version uint64) core.Channel { + return core.Channel{ + ChannelID: escrowChannelID, + UserWallet: wallet, + Asset: "usdc", + Type: core.ChannelTypeEscrow, + BlockchainID: 137, + TokenAddress: "0xtoken456", + ChallengeDuration: 86400, + Nonce: 1, + Status: core.ChannelStatusOpen, + StateVersion: version, + } + } + + newSignedState := func(version uint64, transitionType core.TransitionType, withEscrow bool) core.State { + state := core.State{ + ID: "state1", + Asset: asset, + UserWallet: wallet, + Epoch: 1, + Version: version, + HomeChannelID: ptr(homeChannelID), + Transition: core.Transition{Type: transitionType}, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + UserSig: ptr(userSig), + NodeSig: ptr(nodeSig), + } + if withEscrow { + state.EscrowChannelID = ptr(escrowChannelID) + state.EscrowLedger = &core.Ledger{ + UserBalance: decimal.NewFromInt(500), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + } + } + return state + } + + storeState := func(t *testing.T, store DatabaseStore, state core.State) { + t.Helper() + _, err := store.LockUserState(wallet, asset) + require.NoError(t, err) + require.NoError(t, store.StoreUserState(state, "")) + } + + t.Run("No previous state - allow", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) + + t.Run("Non-escrow transition (TransferSend) - allow", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + + storeState(t, store, newSignedState(1, core.TransitionTypeTransferSend, false)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) + + t.Run("EscrowLock - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(1))) + + storeState(t, store, newSignedState(1, core.TransitionTypeEscrowLock, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow lock is still ongoing") + }) + + t.Run("MutualLock - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(1))) + + storeState(t, store, newSignedState(1, core.TransitionTypeMutualLock, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "mutual lock is still ongoing") + }) + + t.Run("EscrowDeposit - chain caught up - allow", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(2))) + + storeState(t, store, newSignedState(2, core.TransitionTypeEscrowDeposit, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) + + t.Run("EscrowDeposit - Open one-behind (pre-purge happy path) - allow", func(t *testing.T) { + // Signed finalize state is N+1; on-chain escrow channel is at initiate + // version N with Status=Open. This is the protocol-intended steady state + // until the purge queue fires, so receiver-side issuance must not block. + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(1))) + + storeState(t, store, newSignedState(2, core.TransitionTypeEscrowDeposit, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) + + t.Run("EscrowDeposit - Challenged one-behind - block", func(t *testing.T) { + // Signed finalize state is N+1; on-chain channel is Challenged at N. + // Resolution is racing (finalize tx may not land, escrow chain may settle + // at INITIATE) — block receiver-side issuance until status clears. + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + challengedEscrow := newEscrowChannel(1) + challengedEscrow.Status = core.ChannelStatusChallenged + require.NoError(t, store.CreateChannel(challengedEscrow)) + + storeState(t, store, newSignedState(2, core.TransitionTypeEscrowDeposit, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow deposit finalization is still ongoing") + }) + + t.Run("EscrowDeposit - chain more than one version behind - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(0))) + + storeState(t, store, newSignedState(2, core.TransitionTypeEscrowDeposit, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow deposit finalization is still ongoing") + }) + + t.Run("EscrowDeposit - chain version after purge close - allow", func(t *testing.T) { + // Simulates HandleEscrowDepositsPurged: channel marked Closed but + // StateVersion preserved at initiate (N) while signed state is finalize (N+1). + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + purgedEscrow := newEscrowChannel(1) + purgedEscrow.Status = core.ChannelStatusClosed + require.NoError(t, store.CreateChannel(purgedEscrow)) + + storeState(t, store, newSignedState(2, core.TransitionTypeEscrowDeposit, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) + + t.Run("EscrowWithdraw - chain caught up - allow", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(3))) + + storeState(t, store, newSignedState(3, core.TransitionTypeEscrowWithdraw, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) + + t.Run("EscrowWithdraw - chain not synced - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(2))) + + storeState(t, store, newSignedState(3, core.TransitionTypeEscrowWithdraw, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow withdrawal finalization is still ongoing") + }) + + t.Run("EscrowDeposit - escrow channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + + storeState(t, store, newSignedState(2, core.TransitionTypeEscrowDeposit, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow deposit finalization is still ongoing") + }) + + t.Run("EscrowWithdraw - escrow channel missing from DB - block", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + + storeState(t, store, newSignedState(3, core.TransitionTypeEscrowWithdraw, true)) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.Error(t, err) + assert.Contains(t, err.Error(), "escrow withdrawal finalization is still ongoing") + }) + + t.Run("Unsigned state - ignored", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + require.NoError(t, store.CreateChannel(homeChannel)) + require.NoError(t, store.CreateChannel(newEscrowChannel(1))) + + state := newSignedState(2, core.TransitionTypeEscrowLock, true) + state.UserSig = nil + state.NodeSig = nil + storeState(t, store, state) + + err := store.EnsureNoOngoingEscrowOperation(wallet, asset) + require.NoError(t, err) + }) +} + +func ptr[T any](v T) *T { + return &v +} diff --git a/clearnode/store/database/interface.go b/nitronode/store/database/interface.go similarity index 62% rename from clearnode/store/database/interface.go rename to nitronode/store/database/interface.go index 06bb4cbba..98d5fc408 100644 --- a/clearnode/store/database/interface.go +++ b/nitronode/store/database/interface.go @@ -32,7 +32,10 @@ type DatabaseStore interface { GetUserTransactions(wallet string, asset *string, txType *core.TransactionType, fromTime *uint64, toTime *uint64, paginate *core.PaginationParams) ([]core.Transaction, core.PaginationMetadata, error) // RecordTransaction creates a transaction record linking state transitions. - RecordTransaction(tx core.Transaction) error + // applicationID is the self-declared origin tag of the client that caused + // the transaction (see rpc.ApplicationIDQueryParam); empty string means no + // app_id was supplied and the column is persisted as NULL. + RecordTransaction(tx core.Transaction, applicationID string) error // --- Channel Operations --- @@ -43,11 +46,25 @@ type DatabaseStore interface { GetChannelByID(channelID string) (*core.Channel, error) // GetActiveHomeChannel retrieves the active home channel for a user's wallet and asset. + // "Active" includes both Void (DB-only) and Open (materialized onchain). GetActiveHomeChannel(wallet, asset string) (*core.Channel, error) - // CheckOpenChannel verifies if a user has an active channel for the given asset - // and returns the approved signature validators if such a channel exists. - CheckOpenChannel(wallet, asset string) (string, bool, error) + // GetNotClosedHomeChannel retrieves the home channel for a user's wallet and asset + // regardless of status, as long as it has not reached ChannelStatusClosed. Intended + // for read paths (e.g. GetHomeChannel RPC) that must remain functional after an + // off-chain Finalize flips the channel to Closing. + GetNotClosedHomeChannel(wallet, asset string) (*core.Channel, error) + + // CheckActiveChannel verifies if a user has an active home channel for the given asset + // and returns its approved signature validators and current status. A nil status means + // no active channel exists. "Active" includes Void (DB-only, awaiting onchain confirmation) + // and Open (materialized onchain); callers needing onchain materialization must additionally + // require Status == core.ChannelStatusOpen. + CheckActiveChannel(wallet, asset string) (string, *core.ChannelStatus, error) + + // HasNonClosedHomeChannel returns true if any home channel for (wallet, asset) exists + // with a status other than Closed, indicating an in-progress channel lifecycle. + HasNonClosedHomeChannel(wallet, asset string) (bool, error) // UpdateChannel persists changes to a channel's metadata (status, version, etc). UpdateChannel(channel core.Channel) error @@ -69,11 +86,38 @@ type DatabaseStore interface { GetLastUserState(wallet, asset string, signed bool) (*core.State, error) // StoreUserState persists a new user state to the database. - StoreUserState(state core.State) error + // applicationID is the self-declared origin tag of the client that caused + // the transition (see rpc.ApplicationIDQueryParam); empty string means no + // app_id was supplied and the column is persisted as NULL. + StoreUserState(state core.State, applicationID string) error // EnsureNoOngoingStateTransitions validates that no conflicting blockchain operations are pending. EnsureNoOngoingStateTransitions(wallet, asset string) error + // EnsureNoOngoingEscrowOperation validates that the user has no in-flight escrow + // operation (escrow_lock, mutual_lock, or unfinalized escrow_deposit/escrow_withdraw) + // that would prevent issuing a receiver-side state. + EnsureNoOngoingEscrowOperation(wallet, asset string) error + + // UpdateStateSigsIfMissing backfills the user and/or node signatures for a stored + // state when the corresponding column is currently NULL. Either signature may be + // empty to skip that side. + UpdateStateSigsIfMissing(channelID string, version uint64, userSig, nodeSig string) error + + // HasSignedFinalize reports whether a node-signed Finalize state exists for the given + // home channel. Used by event handlers to detect the post-Finalize lifecycle when the + // channel status field has been temporarily overwritten by an on-chain challenge. + HasSignedFinalize(channelID string) (bool, error) + + + // SumNetTransitionAmountAfterVersion returns the net effect on the user's + // home-channel balance of transitions stored against channelID strictly above + // minVersion. Receiver credits (TransferReceive, Release) contribute positively; + // sender debits (TransferSend, Commit) contribute negatively. Other transition + // kinds are excluded. Used to compute the ChallengeRescue amount when a + // challenged channel is closed. + SumNetTransitionAmountAfterVersion(channelID string, minVersion uint64) (decimal.Decimal, error) + // --- Blockchain Action Operations --- // ScheduleInitiateEscrowWithdrawal queues a blockchain action to initiate withdrawal. @@ -84,6 +128,10 @@ type DatabaseStore interface { // This queues the state to be submitted on-chain to update the channel's on-chain state. ScheduleCheckpoint(stateID string, chainID uint64) error + // ScheduleChallenge schedules a challengeChannel(...) submission on the channel's home + // blockchain using the provided state and a node-produced challenger signature. + ScheduleChallenge(stateID string, chainID uint64) error + // ScheduleFinalizeEscrowDeposit schedules a checkpoint for an escrow deposit operation. // This queues the state to be submitted on-chain to finalize an escrow deposit. ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error @@ -154,12 +202,28 @@ type DatabaseStore interface { // RecordLedgerEntry logs a movement of funds within the internal ledger. RecordLedgerEntry(userWallet, accountID, asset string, amount decimal.Decimal) error + // --- Session Key State Pointer Operations --- + + // LockSessionKeyState seeds the pointer row for (user, session_key, kind) if absent and + // locks the (session_key, kind) row for the surrounding transaction. Returns the latest + // stored version for the caller's row and the latestExpiresAt of that version (zero time + // when the latest version is 0, i.e. no history row exists yet). Returns + // ErrSessionKeyNotAllowed if the key is bound to a different wallet for this kind. + // The latestExpiresAt return lets submit handlers distinguish a reactivation + // (prev inactive → submitted active) from a rotation (prev already active) so the + // per-user cap can be re-checked when a revoked slot is brought back. + LockSessionKeyState(userAddress, sessionKey string, kind SessionKeyKind) (latestVersion uint64, latestExpiresAt time.Time, err error) + + // CountSessionKeysForUser returns the number of distinct session keys recorded for the + // wallet across both kinds. Used to enforce the per-user cap at submit time. + CountSessionKeysForUser(userAddress string) (uint32, error) + // --- App Session Key State Operations --- // StoreAppSessionKeyState stores or updates a session key state. StoreAppSessionKeyState(state app.AppSessionKeyStateV1) error - GetAppSessionKeyOwner(sessionKey, appSessionId string) (string, error) + GetAppSessionKeyOwner(sessionKey, appSessionId, applicationId string) (string, error) // SessionKeyStateExists returns the latest version of a non-expired session key state for a user. // Returns 0 if no state exists. @@ -169,8 +233,11 @@ type DatabaseStore interface { // Returns nil if no state exists. GetLastAppSessionKeyState(wallet, sessionKey string) (*app.AppSessionKeyStateV1, error) - // GetLastKeyStates retrieves the latest session key states for a user with optional filtering. - GetLastAppSessionKeyStates(wallet string, sessionKey *string) ([]app.AppSessionKeyStateV1, error) + // GetLastAppSessionKeyStates retrieves the latest session key states for a user with optional + // filtering. When includeInactive is false, only states whose expires_at is in the future are + // returned; when true, all latest states are returned regardless of expiry. Results are + // paginated; totalCount is the unpaginated total matching the filter. + GetLastAppSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]app.AppSessionKeyStateV1, uint32, error) // --- Channel Session Key State Operations --- @@ -182,8 +249,11 @@ type DatabaseStore interface { GetLastChannelSessionKeyVersion(wallet, sessionKey string) (uint64, error) // GetLastChannelSessionKeyStates retrieves the latest channel session key states for a user, - // optionally filtered by session key. - GetLastChannelSessionKeyStates(wallet string, sessionKey *string) ([]core.ChannelSessionKeyStateV1, error) + // optionally filtered by session key. When includeInactive is false, only states whose + // expires_at is in the future are returned; when true, all latest states are returned + // regardless of expiry. Results are paginated; totalCount is the unpaginated total matching + // the filter. + GetLastChannelSessionKeyStates(wallet string, sessionKey *string, includeInactive bool, limit, offset uint32) ([]core.ChannelSessionKeyStateV1, uint32, error) // ValidateChannelSessionKeyForAsset checks that a valid, non-expired session key state // exists at its latest version for the (wallet, sessionKey) pair, includes the given asset, @@ -212,6 +282,18 @@ type DatabaseStore interface { // GetTotalValueLocked computes TVL deltas by domain (channels, app_sessions) and asset, upserts as lifespan metrics, and returns updated totals. GetTotalValueLocked() ([]TotalValueLocked, error) + // SetNodeBalance upserts the on-chain liquidity for a given blockchain and asset. + SetNodeBalance(blockchainID uint64, asset string, value decimal.Decimal) error + + // RefreshUserEnforcedBalance recomputes the locked balance from the user's open home channel on-chain state. + RefreshUserEnforcedBalance(wallet, asset string) error + + // GetNodeBalance returns the on-chain liquidity per blockchain and asset. + GetNodeBalance() ([]NodeBalance, error) + + // GetUserBalanceSummary returns the off-chain liquidity requirement per blockchain and asset. + GetUserBalanceSummary() ([]UserBalanceSummary, error) + // --- User Staked Operations --- // UpdateUserStaked upserts the staked amount for a user on a specific blockchain. @@ -237,6 +319,9 @@ type DatabaseStore interface { // StoreContractEvent stores a blockchain event to prevent duplicate processing. StoreContractEvent(ev core.BlockchainEvent) error - // GetLatestEvent returns the latest block number and log index for a given contract. - GetLatestEvent(contractAddress string, blockchainID uint64) (core.BlockchainEvent, error) + // GetLatestContractEventBlockNumber returns the highest block number for a given contract. + GetLatestContractEventBlockNumber(contractAddress string, blockchainID uint64) (lastBlock uint64, err error) + + // IsContractEventPresent checks if a specific contract event has already been stored. + IsContractEventPresent(blockchainID, blockNumber uint64, txHash string, logIndex uint32) (isPresent bool, err error) } diff --git a/clearnode/store/database/lifespan_metric.go b/nitronode/store/database/lifespan_metric.go similarity index 55% rename from clearnode/store/database/lifespan_metric.go rename to nitronode/store/database/lifespan_metric.go index db3498d9d..099b8c208 100644 --- a/clearnode/store/database/lifespan_metric.go +++ b/nitronode/store/database/lifespan_metric.go @@ -3,17 +3,18 @@ package database import ( "encoding/json" "fmt" + "strconv" "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" + "gorm.io/gorm/clause" ) type LifespanMetric struct { @@ -29,209 +30,6 @@ 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"` @@ -416,6 +214,106 @@ func (s *DBStore) GetLifetimeMetricLastTimestamp(name string) (time.Time, error) return metric.LastTimestamp, nil } +// NodeBalance holds on-chain liquidity for a given blockchain and asset. +type NodeBalance struct { + BlockchainID string `gorm:"column:blockchain_id"` + Asset string `gorm:"column:asset"` + Value decimal.Decimal `gorm:"column:value"` + LastUpdated time.Time `gorm:"column:last_updated"` +} + +// SetNodeBalance upserts the on-chain liquidity for a given blockchain and asset. +func (s *DBStore) SetNodeBalance(blockchainID uint64, asset string, value decimal.Decimal) error { + metricName := "node_balance" + blockchainIDStr := strconv.FormatUint(blockchainID, 10) + + labelsMap := map[string]string{ + "blockchain_id": blockchainIDStr, + "asset": asset, + } + labelsJSON, err := json.Marshal(labelsMap) + if err != nil { + return fmt.Errorf("failed to marshal labels for blockchain_id=%s asset=%s: %w", blockchainIDStr, asset, err) + } + + id, err := getMetricID(metricName, "blockchain_id", blockchainIDStr, "asset", asset) + if err != nil { + return fmt.Errorf("failed to compute metric ID for blockchain_id=%s asset=%s: %w", blockchainIDStr, asset, err) + } + + now := time.Now() + metric := LifespanMetric{ + ID: id, + Name: metricName, + Labels: datatypes.JSON(labelsJSON), + Value: value, + LastTimestamp: now, + UpdatedAt: now, + } + + err = s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "id"}}, + DoUpdates: clause.AssignmentColumns([]string{"value", "last_timestamp", "updated_at"}), + }).Create(&metric).Error + if err != nil { + return fmt.Errorf("failed to upsert node balance metric: %w", err) + } + + return nil +} + +// GetNodeBalance returns the on-chain liquidity per blockchain and asset. +func (s *DBStore) GetNodeBalance() ([]NodeBalance, error) { + metricName := "node_balance" + + var results []NodeBalance + err := s.db.Raw(` + SELECT labels->>'blockchain_id' AS blockchain_id, + 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 node balance metrics: %w", err) + } + + return results, nil +} + +// UserBalanceSummary holds off-chain liquidity metrics for a given blockchain and asset. +// BlockchainID is kept as a string to match the Prometheus exporter's +// label type, avoiding conversions at the call site. +type UserBalanceSummary struct { + BlockchainID string `gorm:"column:home_blockchain_id"` + Asset string `gorm:"column:asset"` + Total decimal.Decimal `gorm:"column:total"` + Underfunded decimal.Decimal `gorm:"column:underfunded"` + Releasable decimal.Decimal `gorm:"column:releasable"` +} + +// GetUserBalanceSummary returns off-chain liquidity metrics per blockchain and asset. +// Results include rows with home_blockchain_id = 0, representing balances not yet +// associated with a home blockchain — useful for surfacing funds that aren't +// attributed to any specific chain. +func (s *DBStore) GetUserBalanceSummary() ([]UserBalanceSummary, error) { + var results []UserBalanceSummary + err := s.db.Raw(` + SELECT CAST(home_blockchain_id AS TEXT) AS home_blockchain_id, asset, + SUM(CAST(balance AS DECIMAL)) AS total, + SUM(CASE WHEN CAST(balance AS DECIMAL) > CAST(enforced AS DECIMAL) THEN CAST(balance AS DECIMAL) - CAST(enforced AS DECIMAL) ELSE 0 END) AS underfunded, + SUM(CASE WHEN CAST(enforced AS DECIMAL) > CAST(balance AS DECIMAL) THEN CAST(enforced AS DECIMAL) - CAST(balance AS DECIMAL) ELSE 0 END) AS releasable + FROM user_balances + GROUP BY home_blockchain_id, asset + `).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to get off-chain liquidity: %w", err) + } + + return results, nil +} + func getMetricID(name string, labels ...string) (string, error) { var labelsArray = []string{} labelsArray = append(labelsArray, labels...) diff --git a/clearnode/store/database/lifespan_metric_test.go b/nitronode/store/database/lifespan_metric_test.go similarity index 59% rename from clearnode/store/database/lifespan_metric_test.go rename to nitronode/store/database/lifespan_metric_test.go index 2ca2521b3..565456c9f 100644 --- a/clearnode/store/database/lifespan_metric_test.go +++ b/nitronode/store/database/lifespan_metric_test.go @@ -1,6 +1,7 @@ package database import ( + "fmt" "testing" "time" @@ -241,3 +242,147 @@ func TestCountActiveAppSessions(t *testing.T) { assert.Equal(t, uint64(3), countByLabel["app2"]) }) } + +func TestGetUserBalanceSummary(t *testing.T) { + t.Run("no balances returns empty", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + results, err := store.GetUserBalanceSummary() + require.NoError(t, err) + assert.Empty(t, results) + }) + + t.Run("computes total, underfunded, and releasable", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + now := time.Now() + // User1: balance=100, enforced=60 → underfunded by 40 + db.Create(&UserBalance{UserWallet: "0xuser1", Asset: "usdc", Balance: decimal.NewFromInt(100), Enforced: decimal.NewFromInt(60), HomeBlockchainID: 1, UpdatedAt: now}) + // User2: balance=50, enforced=80 → releasable by 30 + db.Create(&UserBalance{UserWallet: "0xuser2", Asset: "usdc", Balance: decimal.NewFromInt(50), Enforced: decimal.NewFromInt(80), HomeBlockchainID: 1, UpdatedAt: now}) + // User3: balance=200, enforced=200 → balanced + db.Create(&UserBalance{UserWallet: "0xuser3", Asset: "usdc", Balance: decimal.NewFromInt(200), Enforced: decimal.NewFromInt(200), HomeBlockchainID: 1, UpdatedAt: now}) + + results, err := store.GetUserBalanceSummary() + require.NoError(t, err) + require.Len(t, results, 1) + + r := results[0] + assert.Equal(t, "1", r.BlockchainID) + assert.Equal(t, "usdc", r.Asset) + assert.True(t, decimal.NewFromInt(350).Equal(r.Total)) // 100+50+200 + assert.True(t, decimal.NewFromInt(40).Equal(r.Underfunded)) // only user1: 100-60 + assert.True(t, decimal.NewFromInt(30).Equal(r.Releasable)) // only user2: 80-50 + }) + + t.Run("groups by blockchain and 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), Enforced: decimal.NewFromInt(0), HomeBlockchainID: 1, UpdatedAt: now}) + db.Create(&UserBalance{UserWallet: "0xuser2", Asset: "usdc", Balance: decimal.NewFromInt(50), Enforced: decimal.NewFromInt(0), HomeBlockchainID: 42, UpdatedAt: now}) + db.Create(&UserBalance{UserWallet: "0xuser3", Asset: "eth", Balance: decimal.NewFromInt(10), Enforced: decimal.NewFromInt(5), HomeBlockchainID: 1, UpdatedAt: now}) + + results, err := store.GetUserBalanceSummary() + require.NoError(t, err) + require.Len(t, results, 3) + + totalMap := make(map[string]decimal.Decimal) + for _, r := range results { + key := fmt.Sprintf("%s/%s", r.BlockchainID, r.Asset) + totalMap[key] = r.Total + } + + assert.True(t, decimal.NewFromInt(100).Equal(totalMap["1/usdc"])) + assert.True(t, decimal.NewFromInt(50).Equal(totalMap["42/usdc"])) + assert.True(t, decimal.NewFromInt(10).Equal(totalMap["1/eth"])) + }) + + t.Run("includes unassigned blockchain_id 0", 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), Enforced: decimal.NewFromInt(0), HomeBlockchainID: 1, UpdatedAt: now}) + db.Create(&UserBalance{UserWallet: "0xuser2", Asset: "usdc", Balance: decimal.NewFromInt(75), Enforced: decimal.NewFromInt(0), HomeBlockchainID: 0, UpdatedAt: now}) + + results, err := store.GetUserBalanceSummary() + require.NoError(t, err) + + totalMap := make(map[string]decimal.Decimal) + for _, r := range results { + totalMap[r.BlockchainID] = r.Total + } + + assert.True(t, decimal.NewFromInt(100).Equal(totalMap["1"])) + assert.True(t, decimal.NewFromInt(75).Equal(totalMap["0"])) + }) +} + +func TestSetNodeBalance(t *testing.T) { + t.Run("creates new metric", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + err := store.SetNodeBalance(1, "usdc", decimal.NewFromInt(5000)) + require.NoError(t, err) + + var metric LifespanMetric + err = db.Where("name = ?", "node_balance").First(&metric).Error + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(5000).Equal(metric.Value)) + }) + + t.Run("overwrites previous value", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + require.NoError(t, store.SetNodeBalance(1, "usdc", decimal.NewFromInt(100))) + require.NoError(t, store.SetNodeBalance(1, "usdc", decimal.NewFromInt(200))) + + var metric LifespanMetric + err := db.Where("name = ?", "node_balance").First(&metric).Error + require.NoError(t, err) + assert.True(t, decimal.NewFromInt(200).Equal(metric.Value)) + }) + + t.Run("different chains are separate", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + require.NoError(t, store.SetNodeBalance(1, "usdc", decimal.NewFromInt(100))) + require.NoError(t, store.SetNodeBalance(42, "usdc", decimal.NewFromInt(200))) + + var count int64 + db.Model(&LifespanMetric{}).Where("name = ?", "node_balance").Count(&count) + assert.Equal(t, int64(2), count) + }) +} + +func TestRefreshUserEnforcedBalance(t *testing.T) { + t.Run("sets zero when no open channel", 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), Enforced: decimal.NewFromInt(50), HomeBlockchainID: 1, UpdatedAt: now}) + + err := store.RefreshUserEnforcedBalance("0xuser1", "usdc") + require.NoError(t, err) + + var ub UserBalance + db.Where("user_wallet = ? AND asset = ?", "0xuser1", "usdc").First(&ub) + assert.True(t, decimal.Zero.Equal(ub.Enforced)) + }) +} diff --git a/clearnode/store/database/list_options.go b/nitronode/store/database/list_options.go similarity index 100% rename from clearnode/store/database/list_options.go rename to nitronode/store/database/list_options.go diff --git a/nitronode/store/database/metrics.go b/nitronode/store/database/metrics.go new file mode 100644 index 000000000..de7728b49 --- /dev/null +++ b/nitronode/store/database/metrics.go @@ -0,0 +1,126 @@ +package database + +import ( + "errors" + "fmt" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "gorm.io/gorm" +) + +// QueryDurationObserver records the time a single gorm DB operation took. +// Implemented by the runtime metric exporter to avoid an import cycle +// (the metrics package depends on pkg/{app,core,rpc}, store/database does not). +type QueryDurationObserver interface { + ObserveDBQueryDuration(queryKind string, duration time.Duration) +} + +// metricsStartKey is the gorm.DB context key the before-callback uses to stash +// the operation start timestamp for the after-callback to read. +const metricsStartKey = "nitronode:metrics:start" + +// callbackName is registered on every gorm callback chain we instrument; if +// you change it remember to keep the before/after pair in sync. +const callbackName = "nitronode:metrics" + +// queryKinds is the set of gorm callback chains we hook. Order matches the +// gorm callback registry; "raw" covers Raw / Exec catch-all on the gorm v2 +// callback chain "raw". +var queryKinds = []string{"create", "query", "update", "delete", "row", "raw"} + +// RegisterMetricsCallbacks installs gorm callbacks that observe wall-clock +// duration of each Create / Query / Update / Delete / Row / Raw operation +// onto obs. Pass nil to skip registration (test / sqlite-in-memory cases). +// +// The callbacks add no per-call allocation beyond a single time.Time stashed +// in the gorm context dict. +func RegisterMetricsCallbacks(db *gorm.DB, obs QueryDurationObserver) error { + if db == nil { + return errors.New("database: nil gorm.DB") + } + if obs == nil { + return nil + } + + for _, kind := range queryKinds { + kind := kind // pin for closures + // Explicit case per kind + default that errors out, so adding a new + // entry to queryKinds without an arm here trips at runtime instead + // of silently hooking the wrong gorm processor. (Type of `chain` is + // gorm's unexported *callbacks.processor; inferred from the dummy + // init so the compile-time variable type lines up.) + chain := db.Callback().Query() + switch kind { + case "create": + chain = db.Callback().Create() + case "query": + chain = db.Callback().Query() + case "update": + chain = db.Callback().Update() + case "delete": + chain = db.Callback().Delete() + case "row": + chain = db.Callback().Row() + case "raw": + chain = db.Callback().Raw() + default: + return fmt.Errorf("database: unknown query kind %q", kind) + } + + // Use Replace, not Register: Replace is idempotent on the callback + // name, so calling RegisterMetricsCallbacks twice on the same gorm.DB + // (test helpers, DI restart) doesn't double-fire the after callback + // and double-count duration into the histogram. + if err := chain.Before("*").Replace(callbackName+":before:"+kind, func(tx *gorm.DB) { + tx.Set(metricsStartKey, time.Now()) + }); err != nil { + return err + } + if err := chain.After("*").Replace(callbackName+":after:"+kind, func(tx *gorm.DB) { + v, ok := tx.Get(metricsStartKey) + if !ok { + return + } + start, ok := v.(time.Time) + if !ok { + return + } + obs.ObserveDBQueryDuration(kind, time.Since(start)) + }); err != nil { + return err + } + } + return nil +} + +// RegisterDBStatsCollector registers the standard go_sql_* collector on reg +// for the underlying *sql.DB, so pool-state gauges and wait counters become +// scrapable. Returns an error if the gorm DB doesn't expose a *sql.DB +// (sqlite-in-memory variants normally do; misconfigured pools don't). +// +// Emits, under "nitronode" db name: +// +// go_sql_max_open_connections +// go_sql_open_connections +// go_sql_in_use_connections +// go_sql_idle_connections +// go_sql_wait_count_total +// go_sql_wait_duration_seconds_total +// go_sql_max_idle_closed_total +// go_sql_max_idle_time_closed_total +// go_sql_max_lifetime_closed_total +func RegisterDBStatsCollector(db *gorm.DB, reg prometheus.Registerer) error { + if db == nil { + return errors.New("database: nil gorm.DB") + } + if reg == nil { + return errors.New("database: nil prometheus registerer") + } + sqlDB, err := db.DB() + if err != nil { + return err + } + return reg.Register(collectors.NewDBStatsCollector(sqlDB, "nitronode")) +} diff --git a/clearnode/store/database/state.go b/nitronode/store/database/state.go similarity index 63% rename from clearnode/store/database/state.go rename to nitronode/store/database/state.go index b4191a7e1..01357a973 100644 --- a/clearnode/store/database/state.go +++ b/nitronode/store/database/state.go @@ -21,11 +21,13 @@ const stateSelectColumns = `s.id, s.asset, s.user_wallet, s.epoch, s.version, // UserBalance represents aggregated user balance for an asset type UserBalance struct { - UserWallet string `gorm:"column:user_wallet;primaryKey;size:42"` - Asset string `gorm:"column:asset;primaryKey;size:20"` - Balance decimal.Decimal `gorm:"column:balance;type:varchar(78);not null"` - CreatedAt time.Time `gorm:"column:created_at"` - UpdatedAt time.Time `gorm:"column:updated_at"` + UserWallet string `gorm:"column:user_wallet;primaryKey;size:42"` + Asset string `gorm:"column:asset;primaryKey;size:20"` + Balance decimal.Decimal `gorm:"column:balance;type:varchar(78);not null"` + Enforced decimal.Decimal `gorm:"column:enforced;type:varchar(78);not null;default:0"` + HomeBlockchainID uint64 `gorm:"column:home_blockchain_id;not null;default:0"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` } // TableName specifies the table name for the UserBalance model @@ -69,6 +71,10 @@ type State struct { UserSig *string `gorm:"column:user_sig;type:text"` NodeSig *string `gorm:"column:node_sig;type:text"` + // ApplicationID is the self-declared origin tag of the client that caused + // this state transition (see rpc.ApplicationIDQueryParam). Advisory only. + ApplicationID *string `gorm:"column:application_id;size:66;index:idx_channel_states_app_id"` + // Read-only fields populated from JOINs with channels table HomeBlockchainID *uint64 `gorm:"->;column:home_blockchain_id"` HomeTokenAddress *string `gorm:"->;column:home_token_address"` @@ -128,12 +134,17 @@ func (s *DBStore) GetLastUserState(wallet, asset string, signed bool) (*core.Sta } // StoreUserState persists a new user state to the database. -func (s *DBStore) StoreUserState(state core.State) error { +// applicationID is the client-declared origin tag; empty string → NULL column. +func (s *DBStore) StoreUserState(state core.State, applicationID string) error { dbState, err := coreStateToDB(&state) if err != nil { return fmt.Errorf("failed to encode transitions while creating a db state: %w", err) } + if applicationID != "" { + dbState.ApplicationID = &applicationID + } + if err := s.db.Create(dbState).Error; err != nil { return fmt.Errorf("failed to store user state: %w", err) } @@ -145,8 +156,9 @@ func (s *DBStore) StoreUserState(state core.State) error { err = s.db.Model(&UserBalance{}). Where("user_wallet = ? AND asset = ?", wallet, state.Asset). Updates(map[string]interface{}{ - "balance": balance, - "updated_at": time.Now(), + "balance": balance, + "home_blockchain_id": state.HomeLedger.BlockchainID, + "updated_at": time.Now(), }).Error if err != nil { @@ -209,6 +221,99 @@ func (s *DBStore) GetLastStateByChannelID(channelID string, signed bool) (*core. return databaseStateToCore(&state) } +// UpdateStateSigsIfMissing backfills the user and/or node signatures for a stored state when +// the corresponding column is currently NULL. Used by the on-chain event reactor to repair +// the local record once a state has been enforced on chain. Either signature may be empty, +// in which case that side is skipped. The IS NULL guard keeps the call idempotent on event +// replay and prevents overwriting a signature already populated by the user-facing RPC path. +func (s *DBStore) UpdateStateSigsIfMissing(channelID string, version uint64, userSig, nodeSig string) error { + if userSig == "" && nodeSig == "" { + return nil + } + cid := strings.ToLower(channelID) + if userSig != "" { + res := s.db.Model(&State{}). + Where("(home_channel_id = ? OR escrow_channel_id = ?) AND version = ? AND user_sig IS NULL", cid, cid, version). + Update("user_sig", userSig) + if res.Error != nil { + return fmt.Errorf("failed to backfill user_sig: %w", res.Error) + } + } + if nodeSig != "" { + res := s.db.Model(&State{}). + Where("(home_channel_id = ? OR escrow_channel_id = ?) AND version = ? AND node_sig IS NULL", cid, cid, version). + Update("node_sig", nodeSig) + if res.Error != nil { + return fmt.Errorf("failed to backfill node_sig: %w", res.Error) + } + } + return nil +} + +// HasSignedFinalize reports whether a node-signed Finalize state exists for the given home +// channel. Used by event handlers to detect the post-Finalize lifecycle without loading the +// state row: the channels.status field can be temporarily overwritten by Challenged on a +// stale on-chain challenge, but the Finalize row persists here and is the authoritative +// marker. node_sig IS NOT NULL is checked explicitly so the contract holds even if a future +// path ever stores a Finalize row before the node signs. +func (s *DBStore) HasSignedFinalize(channelID string) (bool, error) { + var count int64 + err := s.db.Model(&State{}). + Where("home_channel_id = ? AND transition_type = ? AND node_sig IS NOT NULL", + strings.ToLower(channelID), uint8(core.TransitionTypeFinalize)). + Count(&count).Error + if err != nil { + return false, fmt.Errorf("failed to check signed finalize: %w", err) + } + return count > 0, nil +} + + +// SumNetTransitionAmountAfterVersion returns the net effect on the user's home-channel +// balance of transitions stored against channelID strictly above minVersion. Receiver +// credits (TransferReceive, Release) contribute positively; sender debits +// (TransferSend, Commit) contribute negatively. Other transition kinds (HomeDeposit, +// HomeWithdrawal, escrow ops, migrate, finalize, acknowledgement) are excluded because +// they either require onchain backing the chain didn't enforce at this closure or +// belong to a different ledger. +// +// Used to compute the ChallengeRescue amount when a challenged channel is closed: +// onchain payout reflects the closure version, anything strictly above didn't enforce, +// and the net amount the user is still owed by the node is the receives minus the +// sends in that interval. Signed (Open-time) and unsigned (during-challenge) rows +// both contribute — both are real value the state-advancer validated at submit time. +// The caller clamps the result at zero before issuing the rescue. +// +// No epoch filter: a channel's in-channel rows live at a single epoch; detached +// post-Finalize rows have home_channel_id NULL and are already excluded by the +// channel_id predicate. +func (s *DBStore) SumNetTransitionAmountAfterVersion(channelID string, minVersion uint64) (decimal.Decimal, error) { + cid := strings.ToLower(channelID) + var row struct { + Net decimal.Decimal `gorm:"column:net"` + } + err := s.db.Raw(` + SELECT COALESCE(SUM(CASE + WHEN transition_type IN (?, ?) THEN transition_amount + WHEN transition_type IN (?, ?) THEN -transition_amount + ELSE 0 + END), 0) AS net + FROM channel_states + WHERE home_channel_id = ? + AND version > ? + `, + uint8(core.TransitionTypeTransferReceive), + uint8(core.TransitionTypeRelease), + uint8(core.TransitionTypeTransferSend), + uint8(core.TransitionTypeCommit), + cid, minVersion, + ).Scan(&row).Error + if err != nil { + return decimal.Zero, fmt.Errorf("failed to compute net transition amount: %w", err) + } + return row.Net, nil +} + // GetStateByChannelIDAndVersion retrieves a specific state version for a channel. // Uses UNION ALL of two indexed queries instead of OR for better performance. func (s *DBStore) GetStateByChannelIDAndVersion(channelID string, version uint64) (*core.State, error) { diff --git a/clearnode/store/database/state_test.go b/nitronode/store/database/state_test.go similarity index 89% rename from clearnode/store/database/state_test.go rename to nitronode/store/database/state_test.go index 08e6e5ddc..74af2795c 100644 --- a/clearnode/store/database/state_test.go +++ b/nitronode/store/database/state_test.go @@ -14,6 +14,62 @@ func TestState_TableName(t *testing.T) { assert.Equal(t, "channel_states", state.TableName()) } +func TestDBStore_StoreUserState_ApplicationID(t *testing.T) { + newState := func(id string) core.State { + homeChannelID := "0xhomechannel123" + return core.State{ + ID: id, + Asset: "USDC", + UserWallet: "0xuserapp", + Epoch: 1, + Version: 1, + HomeChannelID: &homeChannelID, + Transition: core.Transition{ + Type: core.TransitionTypeHomeDeposit, + AccountID: homeChannelID, + Amount: decimal.NewFromInt(1), + }, + HomeLedger: core.Ledger{ + UserBalance: decimal.NewFromInt(1), + UserNetFlow: decimal.NewFromInt(1), + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + } + + t.Run("ApplicationID is persisted when provided", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + _, err := store.LockUserState("0xuserapp", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(newState("state-app"), "my-app")) + + var dbState State + require.NoError(t, db.Where("id = ?", "state-app").First(&dbState).Error) + require.NotNil(t, dbState.ApplicationID) + assert.Equal(t, "my-app", *dbState.ApplicationID) + }) + + t.Run("ApplicationID is NULL when empty", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + _, err := store.LockUserState("0xuserapp", "USDC") + require.NoError(t, err) + + require.NoError(t, store.StoreUserState(newState("state-noapp"), "")) + + var dbState State + require.NoError(t, db.Where("id = ?", "state-noapp").First(&dbState).Error) + assert.Nil(t, dbState.ApplicationID) + }) +} + func TestDBStore_StoreUserState(t *testing.T) { t.Run("Success - Store new state", func(t *testing.T) { db, cleanup := SetupTestDB(t) @@ -47,7 +103,7 @@ func TestDBStore_StoreUserState(t *testing.T) { NodeSig: &nodeSig, } - err := store.StoreUserState(state) + err := store.StoreUserState(state, "") require.NoError(t, err) // Verify state was stored @@ -107,7 +163,7 @@ func TestDBStore_StoreUserState(t *testing.T) { NodeSig: &nodeSig, } - err := store.StoreUserState(state) + err := store.StoreUserState(state, "") require.NoError(t, err) // Verify state was stored @@ -144,11 +200,11 @@ func TestDBStore_StoreUserState(t *testing.T) { }, } - err := store.StoreUserState(state) + err := store.StoreUserState(state, "") require.NoError(t, err) // Try to store again with same ID - err = store.StoreUserState(state) + err = store.StoreUserState(state, "") assert.Error(t, err) }) } @@ -207,8 +263,8 @@ func TestDBStore_GetLastUserState(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) // Get last state result, err := store.GetLastUserState("0xuser123", "USDC", false) @@ -278,8 +334,8 @@ func TestDBStore_GetLastUserState(t *testing.T) { NodeSig: &nodeSig, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) // Get last signed state should return state2 result, err := store.GetLastUserState("0xuser123", "USDC", true) @@ -355,8 +411,8 @@ func TestDBStore_GetLastUserState(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) // Get last state - should prioritize higher epoch result, err := store.GetLastUserState("0xuser123", "USDC", false) @@ -407,7 +463,7 @@ func TestDBStore_GetLastStateByChannelID(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetLastStateByChannelID(homeChannelID, false) require.NoError(t, err) @@ -474,7 +530,7 @@ func TestDBStore_GetLastStateByChannelID(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetLastStateByChannelID(escrowChannelID, false) require.NoError(t, err) @@ -545,8 +601,8 @@ func TestDBStore_GetLastStateByChannelID(t *testing.T) { NodeSig: &nodeSig, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) result, err := store.GetLastStateByChannelID(homeChannelID, true) require.NoError(t, err) @@ -623,8 +679,8 @@ func TestDBStore_GetStateByChannelIDAndVersion(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) // Get version 1 result, err := store.GetStateByChannelIDAndVersion(homeChannelID, 1) @@ -701,7 +757,7 @@ func TestDBStore_GetStateByChannelIDAndVersion(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetStateByChannelIDAndVersion(escrowChannelID, 5) require.NoError(t, err) @@ -750,7 +806,7 @@ func TestDBStore_GetStateByChannelIDAndVersion(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state)) + require.NoError(t, store.StoreUserState(state, "")) result, err := store.GetStateByChannelIDAndVersion(homeChannelID, 999) require.NoError(t, err) diff --git a/clearnode/store/database/test/README.md b/nitronode/store/database/test/README.md similarity index 92% rename from clearnode/store/database/test/README.md rename to nitronode/store/database/test/README.md index d6a348c82..8e85c14ec 100644 --- a/clearnode/store/database/test/README.md +++ b/nitronode/store/database/test/README.md @@ -29,7 +29,7 @@ Apply the migration schema to your test database: ```bash # Navigate to migrations directory -cd clearnode/config/migrations/postgres +cd nitronode/config/migrations/postgres # Apply the migration manually psql -U postgres -d nitrolite_test -f 20251222000000_initial_schema.sql @@ -38,7 +38,7 @@ psql -U postgres -d nitrolite_test -f 20251222000000_initial_schema.sql Or use a migration tool like goose: ```bash -goose -dir clearnode/config/migrations/postgres postgres "user=postgres dbname=nitrolite_test sslmode=disable" up +goose -dir nitronode/config/migrations/postgres postgres "user=postgres dbname=nitrolite_test sslmode=disable" up ``` ## Running the Tests @@ -50,13 +50,13 @@ Set the `POSTGRES_DSN` environment variable and run the tests: export POSTGRES_DSN="host=localhost user=postgres password=postgres dbname=nitrolite_test port=5432 sslmode=disable" # Run all integration tests -go test ./clearnode/store/database/test -v +go test ./nitronode/store/database/test -v # Run specific test -go test ./clearnode/store/database/test -v -run TestPostgres_ChannelOperations +go test ./nitronode/store/database/test -v -run TestPostgres_ChannelOperations # Run tests with cleanup instructions -go test ./clearnode/store/database/test -v -run TestPostgres_PrintCleanupSQL +go test ./nitronode/store/database/test -v -run TestPostgres_PrintCleanupSQL ``` ## Tests Included @@ -102,7 +102,7 @@ After running the tests, you can clean up the test data using the SQL commands p ```bash # Run the cleanup test to see the SQL commands -go test ./clearnode/store/database/test -v -run TestPostgres_PrintCleanupSQL +go test ./nitronode/store/database/test -v -run TestPostgres_PrintCleanupSQL ``` Or manually clean up: diff --git a/clearnode/store/database/test/postgres_integration_test.go b/nitronode/store/database/test/postgres_integration_test.go similarity index 96% rename from clearnode/store/database/test/postgres_integration_test.go rename to nitronode/store/database/test/postgres_integration_test.go index fb6a01f42..76be9150a 100644 --- a/clearnode/store/database/test/postgres_integration_test.go +++ b/nitronode/store/database/test/postgres_integration_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/nitronode/store/database" "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" @@ -113,7 +113,7 @@ func TestPostgres_StateOperations(t *testing.T) { }, } - err := store.StoreUserState(state) + err := store.StoreUserState(state, "") require.NoError(t, err) retrieved, err := store.GetLastUserState(wallet, asset, false) @@ -169,9 +169,9 @@ func TestPostgres_StateOperations(t *testing.T) { }, } - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) - require.NoError(t, store.StoreUserState(state3)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) + require.NoError(t, store.StoreUserState(state3, "")) retrieved, err := store.GetLastUserState(wallet2, asset, false) require.NoError(t, err) @@ -201,7 +201,7 @@ func TestPostgres_TransactionOperations(t *testing.T) { CreatedAt: time.Now(), } - err := store.RecordTransaction(tx1) + err := store.RecordTransaction(tx1, "") require.NoError(t, err) transactions, metadata, err := store.GetUserTransactions(wallet, nil, nil, nil, nil, &core.PaginationParams{}) @@ -236,8 +236,8 @@ func TestPostgres_TransactionOperations(t *testing.T) { CreatedAt: time.Now(), } - require.NoError(t, store.RecordTransaction(tx1)) - require.NoError(t, store.RecordTransaction(tx2)) + require.NoError(t, store.RecordTransaction(tx1, "")) + require.NoError(t, store.RecordTransaction(tx2, "")) assetFilter := "USDC" transactions, _, err := store.GetUserTransactions(wallet2, &assetFilter, nil, nil, nil, &core.PaginationParams{}) @@ -446,8 +446,8 @@ func TestPostgres_UserBalances(t *testing.T) { _, err = store.LockUserState(wallet, "ETH") require.NoError(t, err) - require.NoError(t, store.StoreUserState(state1)) - require.NoError(t, store.StoreUserState(state2)) + require.NoError(t, store.StoreUserState(state1, "")) + require.NoError(t, store.StoreUserState(state2, "")) balances, err := store.GetUserBalances(wallet) require.NoError(t, err) @@ -496,7 +496,7 @@ func TestPostgres_DecimalPrecision(t *testing.T) { }, } - err := store.StoreUserState(state) + err := store.StoreUserState(state, "") require.NoError(t, err) retrieved, err := store.GetLastUserState(wallet, "USDC", false) @@ -524,7 +524,7 @@ func TestPostgres_DecimalPrecision(t *testing.T) { }, } - err := store.StoreUserState(state) + err := store.StoreUserState(state, "") require.NoError(t, err) retrieved, err := store.GetLastUserState(wallet, "ETH", false) diff --git a/clearnode/store/database/testing.go b/nitronode/store/database/testing.go similarity index 87% rename from clearnode/store/database/testing.go rename to nitronode/store/database/testing.go index 1f54bc26a..24be889b6 100644 --- a/clearnode/store/database/testing.go +++ b/nitronode/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(&AppV1{}, &AppLedgerEntryV1{}, &AppSessionV1{}, &AppParticipantV1{}, &BlockchainAction{}, &Channel{}, &ContractEvent{}, &State{}, &Transaction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}) + err = database.AutoMigrate(&AppV1{}, &AppLedgerEntryV1{}, &AppSessionV1{}, &AppParticipantV1{}, &BlockchainAction{}, &Channel{}, &ContractEvent{}, &State{}, &Transaction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &CurrentSessionKeyStateV1{}, &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(&AppV1{}, &AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &Transaction{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}) + err = database.AutoMigrate(&AppV1{}, &AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &State{}, &Transaction{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &CurrentSessionKeyStateV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}) if err != nil { t.Fatalf("Failed to run migrations: %v", err) } diff --git a/clearnode/store/database/transaction.go b/nitronode/store/database/transaction.go similarity index 89% rename from clearnode/store/database/transaction.go rename to nitronode/store/database/transaction.go index dbd1c8daa..d8dd946b9 100644 --- a/clearnode/store/database/transaction.go +++ b/nitronode/store/database/transaction.go @@ -29,6 +29,10 @@ type Transaction struct { ReceiverNewStateID *string `gorm:"column:receiver_new_state_id;size:64"` Amount decimal.Decimal `gorm:"column:amount;type:decimal(38,18);not null"` CreatedAt time.Time + + // ApplicationID is the self-declared origin tag of the client that caused + // this transaction (see rpc.ApplicationIDQueryParam). Advisory only. + ApplicationID *string `gorm:"column:application_id;size:66;index:idx_transactions_app_id"` } func (Transaction) TableName() string { @@ -36,7 +40,8 @@ func (Transaction) TableName() string { } // RecordTransaction creates a transaction record linking state transitions. -func (s *DBStore) RecordTransaction(tx core.Transaction) error { +// applicationID is the client-declared origin tag; empty string → NULL column. +func (s *DBStore) RecordTransaction(tx core.Transaction, applicationID string) error { dbTx := Transaction{ ID: strings.ToLower(tx.ID), Type: tx.TxType, @@ -54,6 +59,9 @@ func (s *DBStore) RecordTransaction(tx core.Transaction) error { lowered := strings.ToLower(*tx.ReceiverNewStateID) dbTx.ReceiverNewStateID = &lowered } + if applicationID != "" { + dbTx.ApplicationID = &applicationID + } if err := s.db.Create(&dbTx).Error; err != nil { return fmt.Errorf("failed to record transaction: %w", err) diff --git a/clearnode/store/database/transaction_test.go b/nitronode/store/database/transaction_test.go similarity index 84% rename from clearnode/store/database/transaction_test.go rename to nitronode/store/database/transaction_test.go index f0a604f93..fe65d889b 100644 --- a/clearnode/store/database/transaction_test.go +++ b/nitronode/store/database/transaction_test.go @@ -35,7 +35,7 @@ func TestDBStore_RecordTransaction(t *testing.T) { CreatedAt: time.Now(), } - err := store.RecordTransaction(tx) + err := store.RecordTransaction(tx, "") require.NoError(t, err) // Verify transaction was recorded @@ -75,7 +75,7 @@ func TestDBStore_RecordTransaction(t *testing.T) { CreatedAt: time.Now(), } - err := store.RecordTransaction(tx) + err := store.RecordTransaction(tx, "") require.NoError(t, err) // Verify transaction was recorded @@ -107,11 +107,11 @@ func TestDBStore_RecordTransaction(t *testing.T) { CreatedAt: time.Now(), } - err := store.RecordTransaction(tx) + err := store.RecordTransaction(tx, "") require.NoError(t, err) // Try to record again with same ID - err = store.RecordTransaction(tx) + err = store.RecordTransaction(tx, "") assert.Error(t, err) }) } @@ -154,9 +154,9 @@ func TestDBStore_GetUserTransactions(t *testing.T) { CreatedAt: time.Now(), } - require.NoError(t, store.RecordTransaction(tx1)) - require.NoError(t, store.RecordTransaction(tx2)) - require.NoError(t, store.RecordTransaction(tx3)) + require.NoError(t, store.RecordTransaction(tx1, "")) + require.NoError(t, store.RecordTransaction(tx2, "")) + require.NoError(t, store.RecordTransaction(tx3, "")) // Get all transactions for user123 pagination := &core.PaginationParams{} @@ -198,8 +198,8 @@ func TestDBStore_GetUserTransactions(t *testing.T) { CreatedAt: time.Now(), } - require.NoError(t, store.RecordTransaction(tx1)) - require.NoError(t, store.RecordTransaction(tx2)) + require.NoError(t, store.RecordTransaction(tx1, "")) + require.NoError(t, store.RecordTransaction(tx2, "")) // Filter by USDC asset := "USDC" @@ -240,8 +240,8 @@ func TestDBStore_GetUserTransactions(t *testing.T) { CreatedAt: time.Now(), } - require.NoError(t, store.RecordTransaction(tx1)) - require.NoError(t, store.RecordTransaction(tx2)) + require.NoError(t, store.RecordTransaction(tx1, "")) + require.NoError(t, store.RecordTransaction(tx2, "")) // Filter by deposit type txType := core.TransactionTypeHomeDeposit @@ -294,9 +294,9 @@ func TestDBStore_GetUserTransactions(t *testing.T) { CreatedAt: baseTime.Add(-1 * time.Second), } - require.NoError(t, store.RecordTransaction(tx1)) - require.NoError(t, store.RecordTransaction(tx2)) - require.NoError(t, store.RecordTransaction(tx3)) + require.NoError(t, store.RecordTransaction(tx1, "")) + require.NoError(t, store.RecordTransaction(tx2, "")) + require.NoError(t, store.RecordTransaction(tx3, "")) // Filter from 2 hours ago to now fromTime := uint64(baseTime.Add(-2 * time.Hour).Unix()) @@ -327,7 +327,7 @@ func TestDBStore_GetUserTransactions(t *testing.T) { Amount: decimal.NewFromInt(int64(i * 100)), CreatedAt: time.Now().Add(time.Duration(i) * time.Minute), } - require.NoError(t, store.RecordTransaction(tx)) + require.NoError(t, store.RecordTransaction(tx, "")) } // Get first page (2 items) @@ -405,10 +405,10 @@ func TestDBStore_GetUserTransactions(t *testing.T) { CreatedAt: baseTime, } - require.NoError(t, store.RecordTransaction(tx1)) - require.NoError(t, store.RecordTransaction(tx2)) - require.NoError(t, store.RecordTransaction(tx3)) - require.NoError(t, store.RecordTransaction(tx4)) + require.NoError(t, store.RecordTransaction(tx1, "")) + require.NoError(t, store.RecordTransaction(tx2, "")) + require.NoError(t, store.RecordTransaction(tx3, "")) + require.NoError(t, store.RecordTransaction(tx4, "")) // Filter: USDC + Deposit + from 1 hour ago asset := "USDC" @@ -455,7 +455,7 @@ func TestDBStore_GetUserTransactions(t *testing.T) { CreatedAt: time.Now(), } - require.NoError(t, store.RecordTransaction(tx)) + require.NoError(t, store.RecordTransaction(tx, "")) pagination := &core.PaginationParams{} transactions, metadata, err := store.GetUserTransactions("0xuser123", nil, nil, nil, nil, pagination) @@ -466,4 +466,51 @@ func TestDBStore_GetUserTransactions(t *testing.T) { assert.Equal(t, "tx1", transactions[0].ID) assert.Equal(t, "0xuser123", transactions[0].ToAccount) }) + + t.Run("ApplicationID is persisted when provided", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + tx := core.Transaction{ + ID: "txapp1", + Asset: "USDC", + TxType: core.TransactionTypeHomeDeposit, + FromAccount: "0xuserapp", + ToAccount: "0xchannelapp", + Amount: decimal.NewFromInt(500), + CreatedAt: time.Now(), + } + + require.NoError(t, store.RecordTransaction(tx, "my-app")) + + var dbTx Transaction + require.NoError(t, db.Where("id = ?", "txapp1").First(&dbTx).Error) + require.NotNil(t, dbTx.ApplicationID) + assert.Equal(t, "my-app", *dbTx.ApplicationID) + }) + + t.Run("ApplicationID is NULL when empty", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + tx := core.Transaction{ + ID: "txnoapp", + Asset: "USDC", + TxType: core.TransactionTypeHomeDeposit, + FromAccount: "0xusernoapp", + ToAccount: "0xchannelnoapp", + Amount: decimal.NewFromInt(500), + CreatedAt: time.Now(), + } + + require.NoError(t, store.RecordTransaction(tx, "")) + + var dbTx Transaction + require.NoError(t, db.Where("id = ?", "txnoapp").First(&dbTx).Error) + assert.Nil(t, dbTx.ApplicationID) + }) } diff --git a/clearnode/store/database/user_staked.go b/nitronode/store/database/user_staked.go similarity index 100% rename from clearnode/store/database/user_staked.go rename to nitronode/store/database/user_staked.go diff --git a/clearnode/store/database/utils.go b/nitronode/store/database/utils.go similarity index 100% rename from clearnode/store/database/utils.go rename to nitronode/store/database/utils.go diff --git a/clearnode/store/memory/asset_config.go b/nitronode/store/memory/asset_config.go similarity index 95% rename from clearnode/store/memory/asset_config.go rename to nitronode/store/memory/asset_config.go index e7ff54ef1..3a00ea5f6 100644 --- a/clearnode/store/memory/asset_config.go +++ b/nitronode/store/memory/asset_config.go @@ -129,6 +129,11 @@ func verifyAssetsConfig(cfg *AssetsConfig) error { } else if !contractAddressRegex.MatchString(token.Address) { return fmt.Errorf("invalid %s token address '%s' for blockchain with id %d", token.Name, token.Address, token.BlockchainID) } + + if asset.Decimals > token.Decimals { + return fmt.Errorf("asset %s decimals (%d) must not exceed token %s decimals (%d) on blockchain %d", + asset.Symbol, asset.Decimals, token.Symbol, token.Decimals, token.BlockchainID) + } } } diff --git a/clearnode/store/memory/asset_config_test.go b/nitronode/store/memory/asset_config_test.go similarity index 64% rename from clearnode/store/memory/asset_config_test.go rename to nitronode/store/memory/asset_config_test.go index 13cea41aa..e15ce63d0 100644 --- a/clearnode/store/memory/asset_config_test.go +++ b/nitronode/store/memory/asset_config_test.go @@ -71,6 +71,57 @@ func TestAssetsConfig_verifyVariables(t *testing.T) { assert.Equal(t, "invalid USD Coin token address '0xinvalid' for blockchain with id 1", err.Error()) }) + // Test asset decimals exceeding token decimals + t.Run("asset decimals exceed token decimals", func(t *testing.T) { + cfg := AssetsConfig{ + Assets: []AssetConfig{ + { + Name: "USD Coin", + Symbol: "USDC", + Decimals: 8, + SuggestedBlockchainID: 1, + Tokens: []TokenConfig{ + { + Name: "USD Coin", + Symbol: "USDC", + BlockchainID: 1, + Address: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + Decimals: 6, + }, + }, + }, + }, + } + err := verifyAssetsConfig(&cfg) + require.Error(t, err) + assert.Equal(t, "asset USDC decimals (8) must not exceed token USDC decimals (6) on blockchain 1", err.Error()) + }) + + // Test asset decimals equal to token decimals (should pass) + t.Run("asset decimals equal to token decimals", func(t *testing.T) { + cfg := AssetsConfig{ + Assets: []AssetConfig{ + { + Name: "USD Coin", + Symbol: "USDC", + Decimals: 6, + SuggestedBlockchainID: 1, + Tokens: []TokenConfig{ + { + Name: "USD Coin", + Symbol: "USDC", + BlockchainID: 1, + Address: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + Decimals: 6, + }, + }, + }, + }, + } + err := verifyAssetsConfig(&cfg) + require.NoError(t, err) + }) + // Test custom symbol for token (inherits from asset when empty) t.Run("custom symbol for token", func(t *testing.T) { cfg := AssetsConfig{ diff --git a/clearnode/store/memory/blockchain_config.go b/nitronode/store/memory/blockchain_config.go similarity index 100% rename from clearnode/store/memory/blockchain_config.go rename to nitronode/store/memory/blockchain_config.go diff --git a/clearnode/store/memory/blockchain_config_test.go b/nitronode/store/memory/blockchain_config_test.go similarity index 100% rename from clearnode/store/memory/blockchain_config_test.go rename to nitronode/store/memory/blockchain_config_test.go diff --git a/clearnode/store/memory/interface.go b/nitronode/store/memory/interface.go similarity index 89% rename from clearnode/store/memory/interface.go rename to nitronode/store/memory/interface.go index 5f0aef35d..df0190014 100644 --- a/clearnode/store/memory/interface.go +++ b/nitronode/store/memory/interface.go @@ -28,4 +28,7 @@ type MemoryStore interface { // GetTokenDecimals returns the decimals for a token on a specific blockchain GetTokenDecimals(blockchainID uint64, tokenAddress string) (uint8, error) + + // GetTokenAsset returns the asset for a token on a specific blockchain + GetTokenAsset(blockchainID uint64, tokenAddress string) (string, error) } diff --git a/clearnode/store/memory/memory_store.go b/nitronode/store/memory/memory_store.go similarity index 82% rename from clearnode/store/memory/memory_store.go rename to nitronode/store/memory/memory_store.go index 18b572e86..75dad40f1 100644 --- a/clearnode/store/memory/memory_store.go +++ b/nitronode/store/memory/memory_store.go @@ -11,10 +11,11 @@ import ( type MemoryStoreV1 struct { 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 + channelSigValidators map[uint64]map[uint8]string // map[blockchain_id]map[validator_id]validator_address + supportedAssets map[string]map[uint64]string // map[asset_symbol]map[blockchain_id]string + tokenAssets map[uint64]map[string]core.Asset // map[blockchain_id]map[token_address]asset + tokenDecimals map[uint64]map[string]uint8 // map[blockchain_id]map[token_address]decimals + assetDecimals map[string]uint8 // map[asset_symbol]decimals } func NewMemoryStoreV1(assetsConfig AssetsConfig, blockchainsConfig map[uint64]BlockchainConfig) (MemoryStore, error) { @@ -49,6 +50,7 @@ func NewMemoryStoreV1(assetsConfig AssetsConfig, blockchainsConfig map[uint64]Bl }) supportedAssets := make(map[string]map[uint64]string) + tokenAssets := make(map[uint64]map[string]core.Asset) tokenDecimals := make(map[uint64]map[string]uint8) assetDecimals := make(map[string]uint8) assets := make([]core.Asset, 0, len(assetsConfig.Assets)) @@ -86,6 +88,15 @@ func NewMemoryStoreV1(assetsConfig AssetsConfig, blockchainsConfig map[uint64]Bl } tokenDecimals[token.BlockchainID][tokenAddress] = token.Decimals + if _, ok := tokenAssets[token.BlockchainID]; !ok { + tokenAssets[token.BlockchainID] = make(map[string]core.Asset) + } + tokenAssets[token.BlockchainID][tokenAddress] = core.Asset{ + Symbol: asset.Symbol, + Name: asset.Name, + Decimals: asset.Decimals, + } + if asset.SuggestedBlockchainID == token.BlockchainID { suggestedBlockchainID = token.BlockchainID } @@ -131,6 +142,7 @@ func NewMemoryStoreV1(assetsConfig AssetsConfig, blockchainsConfig map[uint64]Bl assets: assets, channelSigValidators: channelSigValidators, supportedAssets: supportedAssets, + tokenAssets: tokenAssets, tokenDecimals: tokenDecimals, assetDecimals: assetDecimals, }, nil @@ -235,3 +247,18 @@ func (ms *MemoryStoreV1) GetTokenDecimals(blockchainID uint64, tokenAddress stri } return decimals, nil } + +// GetTokenAsset returns the asset for a token on a specific blockchain +func (ms *MemoryStoreV1) GetTokenAsset(blockchainID uint64, tokenAddress string) (string, error) { + tokenAddress = strings.ToLower(tokenAddress) + + assetsOnChain, ok := ms.tokenAssets[blockchainID] + if !ok { + return "", fmt.Errorf("blockchain with ID '%d' is not supported", blockchainID) + } + asset, ok := assetsOnChain[tokenAddress] + if !ok { + return "", fmt.Errorf("token %s is not supported on blockchain with ID '%d'", tokenAddress, blockchainID) + } + return asset.Symbol, nil +} diff --git a/nitronode/stress/README.md b/nitronode/stress/README.md new file mode 100644 index 000000000..0aa747b84 --- /dev/null +++ b/nitronode/stress/README.md @@ -0,0 +1,469 @@ +# Nitronode Stress Testing Tool + +Built-in stress testing tool for validating nitronode performance, correctness, and stability under load. + +## Quick Start + +```bash +# Set target +export STRESS_WS_URL=ws://localhost:7824/ws + +# Read-only test (no wallet needed) +nitronode stress-test basic ping:1000:10 + +# State-mutating test (funded wallet required) +export STRESS_PRIVATE_KEY= +nitronode stress-test basic transfer-roundtrip:10:20:usdc + +# Storm test (cascading load patterns) +nitronode stress-test storm transfers:3:3:usdc:1 +``` + +## Architecture + +The stress tool is compiled into the nitronode binary and invoked via the `stress-test` subcommand. It connects to a running nitronode instance over WebSocket using the Go SDK. + +``` +nitronode stress-test [args...] + │ + ├── basic ──► Individual method stress tests + │ │ + │ ├── Read-only methods ──► Connection pool (N parallel WebSocket clients) + │ │ │ + │ │ └── Distributes requests round-robin across connections + │ │ + │ └── State-mutating methods ──► Custom orchestration + │ │ + │ ├── transfer-roundtrip: 3-phase fund/stress/collect + │ └── app-session-lifecycle: create/deposit/operate/close + │ + └── storm ──► Cascading tree-based stress patterns + │ + ├── transfers: binary-tree transfer cascade + └── sessions: ternary-growth app session cascade +``` + +**Key design decisions:** +- Each WebSocket connection sends requests sequentially (waits for response before sending next) +- Parallelism is achieved through multiple connections +- Connection pool tolerates individual failures — test runs with whatever connections succeeded +- Results include per-request latency, percentile distribution, and error breakdown + +## Configuration + +All configuration is via environment variables. + +| Variable | Required | Default | Description | +|---|---|---|---| +| `STRESS_WS_URL` | Yes | - | WebSocket URL of the target nitronode | +| `STRESS_PRIVATE_KEY` | No | ephemeral | Hex-encoded ECDSA private key | +| `STRESS_CONNECTIONS` | No | `10` | Default parallel connections per test | +| `STRESS_TIMEOUT` | No | `10m` | Overall test timeout | +| `STRESS_MAX_ERROR_RATE` | No | `0.01` | Error rate threshold (0.01 = 1%) | + +When `STRESS_PRIVATE_KEY` is not set, an ephemeral key is generated. This works for read-only methods but state-mutating methods require a funded wallet. + +## Strategy: basic + +Individual method stress tests targeting specific API endpoints. + +### Spec Format + +``` +nitronode stress-test basic method:total_requests[:connections[:extra_params...]] +``` + +- `method` — test method name +- `total_requests` — total number of operations to execute +- `connections` — parallel WebSocket connections (optional, falls back to `STRESS_CONNECTIONS`) +- `extra_params` — method-specific parameters (asset, amount, wallet address, etc.) + +### Read-Only Methods + +These methods test read path performance. They use a shared connection pool and do not modify server state. An ephemeral wallet is used if `STRESS_PRIVATE_KEY` is not set. + +| Method | Extra Params | Description | +|---|---|---| +| `ping` | none | WebSocket ping/pong roundtrip | +| `get-config` | none | Fetch server configuration | +| `get-blockchains` | none | List available blockchains | +| `get-assets` | `[chain_id]` | List assets, optionally filtered by chain | +| `get-balances` | `[wallet]` | Get wallet balances | +| `get-transactions` | `[wallet]` | Fetch transactions (paginated, limit 20) | +| `get-home-channel` | `asset` or `wallet:asset` | Get home channel for wallet+asset | +| `get-escrow-channel` | `channel_id` | Get escrow channel by ID | +| `get-latest-state` | `asset` or `wallet:asset` | Get latest channel state | +| `get-channel-key-states` | `[wallet]` | Get last channel key states | +| `get-app-sessions` | `[wallet]` | Query app sessions (paginated) | +| `get-app-key-states` | `[wallet]` | Get last app key states | + +**Examples:** + +```bash +# 1000 pings over 10 connections +nitronode stress-test basic ping:1000:10 + +# 500 config fetches over 5 connections +nitronode stress-test basic get-config:500:5 + +# 2000 balance queries over 20 connections +nitronode stress-test basic get-balances:2000:20:0x1234... + +# 1000 home channel lookups +nitronode stress-test basic get-home-channel:1000:10:usdc + +# Asset queries filtered by chain ID +nitronode stress-test basic get-assets:500:5:84532 +``` + +### State-Mutating Methods + +These methods test write path performance. They require `STRESS_PRIVATE_KEY` with a funded wallet. + +#### `transfer-roundtrip` + +Spec: `transfer-roundtrip:rounds:wallets:asset[:amount]` + +| Param | Description | +|---|---| +| `rounds` | Back-and-forth transfer rounds per wallet pair | +| `wallets` | Number of derived wallets (rounded up to even) | +| `asset` | Asset symbol (e.g., `usdc`) | +| `amount` | Transfer amount per operation (default: `0.000001`) | + +**Three-phase execution:** + +1. **Fund** — Sender distributes `amount` to each derived wallet +2. **Stress** — Wallet pairs (0,1), (2,3), ... transfer back and forth in parallel for `rounds` iterations +3. **Collect** — All wallets return funds to sender + +Wallet keys are deterministically derived from the master key using SHA-256: `masterKey:receiver:`. + +Total measured operations = `wallets * rounds` (phase 2 only). + +**Examples:** + +```bash +# 10 rounds, 20 wallets (10 pairs), usdc, default amount +nitronode stress-test basic transfer-roundtrip:10:20:usdc + +# 50 rounds, 100 wallets (50 pairs), custom amount +nitronode stress-test basic transfer-roundtrip:50:100:usdc:0.0001 +``` + +#### `app-session-lifecycle` + +Spec: `app-session-lifecycle:sessions:participants:operates:asset[:amount]` + +| Param | Description | +|---|---| +| `sessions` | Number of concurrent app session lifecycles | +| `participants` | Wallets per session (quorum = all must sign) | +| `operates` | Number of operate state updates per session | +| `asset` | Asset symbol (e.g., `usdc`) | +| `amount` | Deposit amount per session (default: `0.000003`) | + +**Per-session lifecycle:** + +1. **Create** — Create app session with all participants +2. **Deposit** — First participant deposits funds into session +3. **Operate** — Submit `N` state updates with rotating fund allocations +4. **Close** — Close session with final allocation matching last operate + +All signatures are pre-generated before the stress phase begins. Each session is driven by its first participant ("pipe lead") over a dedicated WebSocket connection. + +Wallet keys are derived using SHA-256: `masterKey:appsession::`. + +Total measured operations = `sessions * (operates + 3)`. + +**Examples:** + +```bash +# 10 sessions, 5 participants each, 3 operates per session +nitronode stress-test basic app-session-lifecycle:10:5:3:usdc + +# 50 sessions, 3 participants, 10 operates, custom amount +nitronode stress-test basic app-session-lifecycle:50:3:10:usdc:0.000005 +``` + +## Strategy: storm + +Cascading tree-based stress patterns that simulate realistic fund distribution and collection flows. All storm methods require `STRESS_PRIVATE_KEY` with a funded wallet. + +### `transfers` + +Binary-tree transfer cascade. Each iteration doubles the number of active wallets via parallel transfers. After the tree is fully built, an optional plateau phase bounces last-layer transfers back and forth for sustained load at maximum parallelism. + +Spec: `nitronode stress-test storm transfers::::` + +| Param | Description | +|---|---| +| `iterations` | Number of cascade levels | +| `cycles` | Number of plateau back-and-forth cycles (0 to skip) | +| `asset` | Asset symbol (e.g., `usdc`) | +| `amount` | Amount per leaf-level transfer | + +**How it works:** + +The origin wallet sits at the root of a binary tree. Each forward iteration, every active wallet transfers to a new child, doubling the active set. After reaching the iteration limit, the plateau phase bounces the last-layer transfers back and forth for the given number of cycles. Finally, the reverse phase collects all funds back up the tree. + +- Total wallets: `2^iterations` (including origin) +- Total transfers: `2 * (2^iterations - 1) + cycles * 2 * 2^(iterations-1)` +- Origin needs: `amount * 2^iterations` of the asset +- Connections are established lazily per-iteration and closed after reverse to avoid connection limits + +**Example with 3 iterations, 2 cycles, and 1 usdc:** + +``` +Forward: + Iteration 1: A -> B (4 usdc) + Iteration 2: A -> C (2), B -> D (2) + Iteration 3: A -> E (1), B -> F (1), C -> G (1), D -> H (1) + +Plateau: + Cycle 1 back: E -> A, F -> B, G -> C, H -> D + Cycle 1 forth: A -> E, B -> F, C -> G, D -> H + Cycle 2 back: E -> A, F -> B, G -> C, H -> D + Cycle 2 forth: A -> E, B -> F, C -> G, D -> H + +Reverse: + Iteration 3: E -> A, F -> B, G -> C, H -> D + Iteration 2: C -> A, D -> B + Iteration 1: B -> A +``` + +Within each iteration/cycle, all transfers run in parallel. Stops immediately on any failure. + +```bash +# 3 iterations, no plateau (8 wallets, 14 transfers) +nitronode stress-test storm transfers:3:0:usdc:1 + +# 3 iterations, 5 plateau cycles (8 wallets, 54 transfers) +nitronode stress-test storm transfers:3:5:usdc:1 + +# 5 iterations, 10 plateau cycles (32 wallets, 382 transfers) +nitronode stress-test storm transfers:5:10:usdc:0.001 +``` + +### `sessions` + +Ternary-growth app session cascade. Each iteration triples the number of active wallets via 3-participant app sessions. After the tree is fully built, an optional plateau phase bounces last-layer sessions back and forth for sustained load. + +Spec: `nitronode stress-test storm sessions::::` + +| Param | Description | +|---|---| +| `iterations` | Number of cascade levels | +| `cycles` | Number of plateau back-and-forth cycles (0 to skip) | +| `asset` | Asset symbol (e.g., `usdc`) | +| `amount` | Amount per leaf-level allocation | + +**How it works:** + +Each iteration, every existing wallet opens a 3-participant app session with 2 new child wallets. The parent deposits funds, reallocates to children, and closes the session. This triples the active wallet set per iteration. After all iterations, the plateau phase bounces last-layer sessions back and forth. Finally, the reverse phase collects all funds back up the tree. + +- Total wallets: `3^iterations` (including origin) +- Origin needs: `amount * 3^iterations` of the asset +- Each app session goes through: create -> deposit -> reallocate -> close +- Connections are established lazily per-iteration and closed after reverse +- A unique app ID is generated per execution to prevent nonce collisions +- Wallets that receive funds via session close are acknowledged once to open a channel (before their first deposit) + +**Forward session lifecycle (4 measured ops):** +1. Create session with parent + 2 children +2. Parent deposits `2 * amount * 3^(iterations - i)` into session +3. Reallocate: parent -> child1 and parent -> child2 +4. Close session + +New parents are acknowledged before each forward iteration (from iteration 2 onward) to open their channels. + +**Plateau cycle:** +- Back (5 ops): children deposit, reallocate to parent, close +- Forth (4 ops): parent deposits, reallocate to children, close + +All last-layer wallets are acknowledged once before the plateau/reverse begins. + +**Reverse session lifecycle (5 measured ops):** +1. Create session with same 3 participants +2. Child1 deposits its balance back +3. Child2 deposits its balance back +4. Reallocate: children -> parent +5. Close session + +**Example with 2 iterations, 2 cycles, and 1 usdc:** + +``` +Forward: + Iteration 1: session(A,B,C) — A deposits 6, reallocates 3 to B, 3 to C + Iteration 2: session(A,D,E), session(B,F,G), session(C,H,I) — each deposits 2, reallocates 1 each + +Plateau: + Cycle 1 back: D,E -> A; F,G -> B; H,I -> C + Cycle 1 forth: A -> D,E; B -> F,G; C -> H,I + Cycle 2 back: D,E -> A; F,G -> B; H,I -> C + Cycle 2 forth: A -> D,E; B -> F,G; C -> H,I + +Reverse: + Iteration 2: D,E -> A; F,G -> B; H,I -> C + Iteration 1: B,C -> A +``` + +Within each iteration/cycle, all sessions run in parallel. Stops immediately on any failure. + +```bash +# 2 iterations, no plateau (9 wallets) +nitronode stress-test storm sessions:2:0:usdc:1 + +# 2 iterations, 5 plateau cycles (9 wallets) +nitronode stress-test storm sessions:2:5:usdc:1 + +# 3 iterations, 10 plateau cycles (27 wallets) +nitronode stress-test storm sessions:3:10:usdc:0.001 +``` + +## Report Output + +Every test produces a standardized report: + +``` +Stress Test Report +================== +Method: ping +Total Requests: 1000 +Connections: 10 +Duration: 2.345s + +Results +------- +Successful: 998 (99.8%) +Failed: 2 (0.2%) +Requests/sec: 426.44 + +Latency +------- +Min: 1.2ms +Max: 45.3ms +Average: 2.3ms +Median (p50): 2.1ms +P95: 4.5ms +P99: 12.8ms + +Errors +------ + context deadline exceeded 2 +``` + +**Pass/fail criteria:** The test exits with code 0 (PASS) if the error rate is within `STRESS_MAX_ERROR_RATE`, or code 1 (FAIL) if exceeded. Storm tests exit 0 on success or 1 on any transfer/session failure. + +## Helm Integration + +The stress tool is integrated as a Helm test. When enabled, `helm test` creates a Pod that runs the stress spec against the in-cluster nitronode service. + +**values.yaml:** + +```yaml +stressTest: + enabled: true + specs: + - "basic ping:100000:100" + privateKey: "" # optional, for state-mutating tests + connections: 10 + timeout: "10m" + maxErrorRate: "0.01" +``` + +**Run:** + +```bash +helm test +``` + +The WebSocket URL defaults to the in-cluster service (`ws://-nitronode:7824/ws`). Override with `stressTest.wsURL` for external targets. + +## Testing Strategy + +### Phase 1: Read Path Baseline + +Validate read performance under increasing load. No funded wallet needed. + +```bash +export STRESS_WS_URL=ws://target:7824/ws + +# Baseline latency +nitronode stress-test basic ping:100:1 +nitronode stress-test basic get-config:100:1 + +# Scale connections +nitronode stress-test basic ping:10000:10 +nitronode stress-test basic ping:100000:100 +nitronode stress-test basic get-balances:10000:50:0xWALLET +``` + +### Phase 2: Write Path Stress + +Test state mutation throughput. Requires funded wallet. + +```bash +export STRESS_PRIVATE_KEY= + +# Small scale +nitronode stress-test basic transfer-roundtrip:5:4:usdc + +# Production scale +nitronode stress-test basic transfer-roundtrip:50:100:usdc:0.0001 +``` + +### Phase 3: App Session Lifecycle + +Test multi-participant coordination. + +```bash +# Small scale +nitronode stress-test basic app-session-lifecycle:5:3:3:usdc + +# Production scale +nitronode stress-test basic app-session-lifecycle:50:5:10:usdc:0.000005 +``` + +### Phase 4: Storm Tests + +Test cascading fund distribution and collection under realistic multi-wallet scenarios. + +```bash +# Transfer cascade — 3 levels, no plateau +nitronode stress-test storm transfers:3:0:usdc:1 + +# Transfer cascade — 3 levels, 10 plateau cycles for sustained load +nitronode stress-test storm transfers:3:10:usdc:1 + +# App session cascade — 2 levels, no plateau +nitronode stress-test storm sessions:2:0:usdc:1 + +# App session cascade — 2 levels, 5 plateau cycles +nitronode stress-test storm sessions:2:5:usdc:1 +``` + +### Phase 5: Sustained Load + +Run extended tests to detect resource leaks and degradation. + +```bash +# High volume read +nitronode stress-test basic ping:1000000:100 + +# Extended write +nitronode stress-test basic transfer-roundtrip:500:100:usdc:0.000001 +``` + +## Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| `STRESS_WS_URL is required` | Missing environment variable | Set `STRESS_WS_URL` | +| `failed to open any connections` | Target unreachable or refusing connections | Verify URL and that nitronode is running | +| `WARNING: Only N/M connections established` | Server under load or connection limits | Reduce connection count or check server capacity | +| `STRESS_PRIVATE_KEY is required` | State-mutating method without key | Set `STRESS_PRIVATE_KEY` with a funded wallet | +| `fund wallet X: insufficient balance` | Sender wallet not funded | Transfer funds to the wallet address printed at startup | +| High error rate in transfer tests | Database contention or deadlocks | Check server logs for deadlock traces | +| `context deadline exceeded` | Test exceeded `STRESS_TIMEOUT` | Increase timeout or reduce test scope | +| Storm nonce collision | Running multiple storm sessions concurrently | Each run generates a unique app ID automatically | diff --git a/clearnode/stress/app_session.go b/nitronode/stress/app_session.go similarity index 100% rename from clearnode/stress/app_session.go rename to nitronode/stress/app_session.go diff --git a/clearnode/stress/config.go b/nitronode/stress/config.go similarity index 100% rename from clearnode/stress/config.go rename to nitronode/stress/config.go diff --git a/clearnode/stress/methods.go b/nitronode/stress/methods.go similarity index 100% rename from clearnode/stress/methods.go rename to nitronode/stress/methods.go diff --git a/clearnode/stress/pool.go b/nitronode/stress/pool.go similarity index 97% rename from clearnode/stress/pool.go rename to nitronode/stress/pool.go index a84050f39..a10c3327d 100644 --- a/clearnode/stress/pool.go +++ b/nitronode/stress/pool.go @@ -9,7 +9,7 @@ import ( sdk "github.com/layer-3/nitrolite/sdk/go" ) -// CreateClientPool opens up to n WebSocket connections to the clearnode. +// CreateClientPool opens up to n WebSocket connections to the nitronode. // It tolerates individual connection failures and returns whatever connections // succeeded. Returns an error only if zero connections could be established. func CreateClientPool(wsURL, privateKey string, n int) ([]*sdk.Client, error) { diff --git a/clearnode/stress/report.go b/nitronode/stress/report.go similarity index 89% rename from clearnode/stress/report.go rename to nitronode/stress/report.go index 034290691..d69529a0c 100644 --- a/clearnode/stress/report.go +++ b/nitronode/stress/report.go @@ -42,9 +42,13 @@ func ComputeReport(method string, totalReqs, connections int, results []Result, report.MedianLatency = durationPercentile(durations, 50) report.P95Latency = durationPercentile(durations, 95) report.P99Latency = durationPercentile(durations, 99) - // Uses only successful request latencies so that timeouts and - // straggler failures don't deflate the measured server throughput. - report.RequestsPerSec = float64(connections) / report.AvgLatency.Seconds() + if connections > 0 { + // Pool-based: theoretical throughput = connections / avg latency. + report.RequestsPerSec = float64(connections) / report.AvgLatency.Seconds() + } else { + // Custom orchestration (storm): measured throughput = successful / wall time. + report.RequestsPerSec = float64(report.Successful) / totalTime.Seconds() + } } return report diff --git a/clearnode/stress/run.go b/nitronode/stress/run.go similarity index 71% rename from clearnode/stress/run.go rename to nitronode/stress/run.go index 2cf367c80..4c9f76aa2 100644 --- a/clearnode/stress/run.go +++ b/nitronode/stress/run.go @@ -18,6 +18,26 @@ func Run(args []string) int { return 1 } + switch args[0] { + case "basic": + return runBasic(args[1:]) + case "storm": + return runStorm(args[1:]) + default: + fmt.Fprintf(os.Stderr, "ERROR: Unknown strategy %q\n", args[0]) + fmt.Fprintf(os.Stderr, "Available strategies: basic, storm\n\n") + printUsage() + return 1 + } +} + +// runBasic runs the original stress test strategy. +func runBasic(args []string) int { + if len(args) == 0 { + printBasicUsage() + return 1 + } + cfg, err := ReadConfig() if err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) @@ -110,12 +130,26 @@ func parseSpec(arg string, defaultConns int) (TestSpec, error) { } func printUsage() { - fmt.Println("Usage: clearnode stress-test ") + fmt.Println("Usage: nitronode stress-test [args...]") + fmt.Println() + fmt.Println("Available strategies:") + fmt.Println(" basic Run individual method stress tests") + fmt.Println(" storm Run storm stress tests") + fmt.Println() + fmt.Println("Examples:") + fmt.Println(" nitronode stress-test basic ping:1000:10") + fmt.Println(" nitronode stress-test storm ...") + fmt.Println() + fmt.Println("Run 'nitronode stress-test ' for strategy-specific help.") +} + +func printBasicUsage() { + fmt.Println("Usage: nitronode stress-test basic ") fmt.Println() fmt.Println("Spec format: method:total_requests[:connections[:extra_params...]]") fmt.Println() fmt.Println("Environment variables:") - fmt.Println(" STRESS_WS_URL (required) WebSocket URL of the clearnode") + fmt.Println(" STRESS_WS_URL (required) WebSocket URL of the nitronode") fmt.Println(" STRESS_PRIVATE_KEY (optional) Hex private key for signing (ephemeral if not set)") fmt.Println(" STRESS_CONNECTIONS (optional) Default connections per test (default: 10)") fmt.Println(" STRESS_TIMEOUT (optional) Per-test timeout (default: 10m)") @@ -124,20 +158,20 @@ func printUsage() { fmt.Println("Available methods:", strings.Join(sortedMethodNames(), ", ")) fmt.Println() fmt.Println("Read-only methods (spec = method:requests:connections[:params]):") - fmt.Println(" clearnode stress-test ping:1000:10") - fmt.Println(" clearnode stress-test get-config:500:5") - fmt.Println(" clearnode stress-test get-balances:2000:20:0xWALLET") - fmt.Println(" clearnode stress-test get-home-channel:1000:10:usdc") + fmt.Println(" nitronode stress-test basic ping:1000:10") + fmt.Println(" nitronode stress-test basic get-config:500:5") + fmt.Println(" nitronode stress-test basic get-balances:2000:20:0xWALLET") + fmt.Println(" nitronode stress-test basic get-home-channel:1000:10:usdc") fmt.Println() fmt.Println("State-mutating methods (require STRESS_PRIVATE_KEY with funded wallet):") fmt.Println() fmt.Println(" transfer-roundtrip:rounds:wallets:asset[:amount]") - fmt.Println(" clearnode stress-test transfer-roundtrip:10:100:usdc") - fmt.Println(" clearnode stress-test transfer-roundtrip:10:100:usdc:0.0001") + fmt.Println(" nitronode stress-test basic transfer-roundtrip:10:100:usdc") + fmt.Println(" nitronode stress-test basic transfer-roundtrip:10:100:usdc:0.0001") fmt.Println() fmt.Println(" app-session-lifecycle:sessions:participants:operates:asset[:amount]") - fmt.Println(" clearnode stress-test app-session-lifecycle:10:5:3:usdc") - fmt.Println(" clearnode stress-test app-session-lifecycle:10:5:3:usdc:0.000005") + fmt.Println(" nitronode stress-test basic app-session-lifecycle:10:5:3:usdc") + fmt.Println(" nitronode stress-test basic app-session-lifecycle:10:5:3:usdc:0.000005") } func sortedMethodNames() []string { diff --git a/clearnode/stress/runner.go b/nitronode/stress/runner.go similarity index 100% rename from clearnode/stress/runner.go rename to nitronode/stress/runner.go diff --git a/nitronode/stress/storm.go b/nitronode/stress/storm.go new file mode 100644 index 000000000..d5536b314 --- /dev/null +++ b/nitronode/stress/storm.go @@ -0,0 +1,1050 @@ +package stress + +import ( + "context" + "fmt" + "math" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/sign" +) + +// runStorm is the entry point for the "storm" stress test strategy. +// Currently supports: transfers, sessions. +func runStorm(args []string) int { + if len(args) == 0 { + printStormUsage() + return 1 + } + + parts := strings.Split(args[0], ":") + if len(parts) < 1 { + printStormUsage() + return 1 + } + + switch parts[0] { + case "transfers": + return runTransferStorm(parts[1:]) + case "sessions": + return runSessionStorm(parts[1:]) + default: + fmt.Fprintf(os.Stderr, "ERROR: Unknown storm method %q\n", parts[0]) + printStormUsage() + return 1 + } +} + +// runTransferStorm implements the binary-tree transfer storm. +// +// Spec: transfers:iterations:cycles:asset:amount +// +// The test creates a binary tree of wallets. In each forward iteration, +// every active wallet transfers to a new child wallet, doubling the active set. +// After reaching the iteration limit, a plateau phase runs the last-layer +// transfers back and forth for the given number of cycles. Finally, the +// reverse phase collects all funds back up the tree to the origin wallet. +func runTransferStorm(parts []string) int { + if len(parts) < 4 { + fmt.Fprintf(os.Stderr, "ERROR: transfers requires iterations, cycles, asset, and amount\n") + fmt.Fprintf(os.Stderr, "Usage: nitronode stress-test storm transfers::::\n") + return 1 + } + + iterations, err := strconv.Atoi(parts[0]) + if err != nil || iterations <= 0 { + fmt.Fprintf(os.Stderr, "ERROR: invalid iterations %q: must be positive integer\n", parts[0]) + return 1 + } + + cycles, err := strconv.Atoi(parts[1]) + if err != nil || cycles < 0 { + fmt.Fprintf(os.Stderr, "ERROR: invalid cycles %q: must be non-negative integer\n", parts[1]) + return 1 + } + + asset := parts[2] + + amount, err := decimal.NewFromString(parts[3]) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: invalid amount %q: %v\n", parts[3], err) + return 1 + } + + if os.Getenv("STRESS_PRIVATE_KEY") == "" { + fmt.Fprintf(os.Stderr, "ERROR: STRESS_PRIVATE_KEY is required for storm transfers (origin must have funds)\n") + return 1 + } + + cfg, err := ReadConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + return 1 + } + + originAddr, err := cfg.WalletAddress() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + return 1 + } + + totalNodes := int(math.Pow(2, float64(iterations))) + derivedWallets := totalNodes - 1 + lastLayerSize := int(math.Pow(2, float64(iterations-1))) + plateauTransfers := cycles * 2 * lastLayerSize // each cycle = back + forth + totalTransfers := 2*derivedWallets + plateauTransfers + originAmount := amount.Mul(decimal.NewFromInt(int64(totalNodes))) + + fmt.Printf("Transfer Storm\n") + fmt.Printf(" Origin: %s\n", originAddr) + fmt.Printf(" Iterations: %d\n", iterations) + fmt.Printf(" Cycles: %d\n", cycles) + fmt.Printf(" Asset: %s\n", asset) + fmt.Printf(" Amount/leaf: %s\n", amount.String()) + fmt.Printf(" Origin needs: %s %s\n", originAmount.String(), asset) + fmt.Printf(" Wallets: %d (derived)\n", derivedWallets) + fmt.Printf(" Transfers: %d total (%d forward + %d plateau + %d reverse)\n", + totalTransfers, derivedWallets, plateauTransfers, derivedWallets) + + ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout) + defer cancel() + + // Build wallet tree: origin at index 0, derived wallets at 1..totalNodes-1. + // Binary tree: node i has children 2i+1 and 2i+2. + // Connections are established lazily per-iteration to avoid hitting connection limits. + wallets := make([]wallet, totalNodes) + + // Derive all wallets upfront (cheap, no connections). + fmt.Printf(" Deriving %d wallets...\n", derivedWallets) + wallets[0] = wallet{key: cfg.PrivateKey, addr: originAddr} + for i := 1; i < totalNodes; i++ { + key := deriveKey(cfg.PrivateKey, i-1) + addr, err := walletAddressFromKey(key) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to derive wallet %d: %v\n", i, err) + return 1 + } + wallets[i] = wallet{key: key, addr: addr} + } + + // Connect origin. + fmt.Println(" Connecting origin...") + originClient, err := createClient(cfg.WsURL, cfg.PrivateKey) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to connect origin: %v\n", err) + return 1 + } + wallets[0].client = originClient + defer func() { + for _, w := range wallets { + if w.client != nil { + w.client.Close() + } + } + }() + + var results []Result + start := time.Now() + + // Forward phase: iterations 1..iterations + // Iteration i: 2^(i-1) senders, each transfers amount * 2^(iterations-i) to its child. + // Before each iteration, connect the recipients that will be needed. + for iter := 1; iter <= iterations; iter++ { + sendersCount := int(math.Pow(2, float64(iter-1))) + transferAmount := amount.Mul(decimal.NewFromInt(int64(math.Pow(2, float64(iterations-iter))))) + + // Connect recipients for this iteration. + // Children at iteration i occupy indices 2^(i-1) .. 2^i - 1. + layerStart := int(math.Pow(2, float64(iter-1))) + layerEnd := int(math.Pow(2, float64(iter))) - 1 + if layerEnd >= totalNodes { + layerEnd = totalNodes - 1 + } + fmt.Printf(" Connecting wallets %d..%d for iteration %d...\n", layerStart, layerEnd, iter) + for i := layerStart; i <= layerEnd; i++ { + c, err := createClient(cfg.WsURL, wallets[i].key) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to connect wallet %d: %v\n", i, err) + return 1 + } + wallets[i].client = c + time.Sleep(10 * time.Millisecond) + } + + fmt.Printf(" Forward iteration %d/%d: %d transfers of %s %s\n", + iter, iterations, sendersCount, transferAmount.String(), asset) + + iterResults := make([]Result, sendersCount) + var wg sync.WaitGroup + + for s := range sendersCount { + if ctx.Err() != nil { + fmt.Fprintf(os.Stderr, "\nERROR: context cancelled: %v\n", ctx.Err()) + return 1 + } + + wg.Add(1) + go func(senderIdx int) { + defer wg.Done() + + // Senders at iteration i are all existing nodes: indices 0..2^(i-1)-1. + // Children at iteration i: indices 2^(i-1)..2^i-1. + // Sender senderIdx maps to child at 2^(iter-1) + senderIdx. + senderNodeIdx := senderIdx + childNodeIdx := int(math.Pow(2, float64(iter-1))) + senderIdx + if childNodeIdx >= totalNodes { + return + } + + t := time.Now() + _, err := wallets[senderNodeIdx].client.Transfer(ctx, wallets[childNodeIdx].addr, asset, transferAmount) + iterResults[senderIdx] = Result{Duration: time.Since(t), Err: err} + if err != nil { + fmt.Fprintf(os.Stderr, "\n Transfer failed at iteration %d, sender %d: %v\n", iter, senderIdx, err) + } + }(s) + } + wg.Wait() + + if failed := collectErrors(iterResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: transfer failed during forward phase: %v\n", e) + } + printStormReport("storm-transfers", results, time.Since(start)) + return 1 + } + results = append(results, iterResults...) + } + + // Plateau phase: last-layer transfers bounce back and forth. + if cycles > 0 { + fmt.Printf(" Plateau phase: %d cycles of %d back-and-forth transfers...\n", cycles, lastLayerSize) + parentStart := 0 + childStart := int(math.Pow(2, float64(iterations-1))) + + for c := 1; c <= cycles; c++ { + // Back: children → parents + fmt.Printf(" Plateau %d back: %d transfers of %s %s\n", c, lastLayerSize, amount.String(), asset) + backResults := make([]Result, lastLayerSize) + var wg sync.WaitGroup + for s := range lastLayerSize { + wg.Add(1) + go func(idx int) { + defer wg.Done() + childIdx := childStart + idx + parentIdx := parentStart + idx + t := time.Now() + _, err := wallets[childIdx].client.Transfer(ctx, wallets[parentIdx].addr, asset, amount) + backResults[idx] = Result{Duration: time.Since(t), Err: err} + }(s) + } + wg.Wait() + if failed := collectErrors(backResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: transfer failed during plateau back: %v\n", e) + } + printStormReport("storm-transfers", results, time.Since(start)) + return 1 + } + results = append(results, backResults...) + + // Forth: parents → children + fmt.Printf(" Plateau %d forth: %d transfers of %s %s\n", c, lastLayerSize, amount.String(), asset) + forthResults := make([]Result, lastLayerSize) + for s := range lastLayerSize { + wg.Add(1) + go func(idx int) { + defer wg.Done() + parentIdx := parentStart + idx + childIdx := childStart + idx + t := time.Now() + _, err := wallets[parentIdx].client.Transfer(ctx, wallets[childIdx].addr, asset, amount) + forthResults[idx] = Result{Duration: time.Since(t), Err: err} + }(s) + } + wg.Wait() + if failed := collectErrors(forthResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: transfer failed during plateau forth: %v\n", e) + } + printStormReport("storm-transfers", results, time.Since(start)) + return 1 + } + results = append(results, forthResults...) + } + } + + fmt.Println(" Plateau complete, starting reverse (cleanup)...") + + // Reverse phase: iterations countdown from `iterations` to 1. + // Each child sends funds back to its parent. + for iter := iterations; iter >= 1; iter-- { + sendersCount := int(math.Pow(2, float64(iter-1))) + transferAmount := amount.Mul(decimal.NewFromInt(int64(math.Pow(2, float64(iterations-iter))))) + fmt.Printf(" Reverse iteration %d/%d: %d transfers of %s %s\n", + iter, iterations, sendersCount, transferAmount.String(), asset) + + iterResults := make([]Result, sendersCount) + var wg sync.WaitGroup + + for s := range sendersCount { + if ctx.Err() != nil { + fmt.Fprintf(os.Stderr, "\nERROR: context cancelled: %v\n", ctx.Err()) + return 1 + } + + wg.Add(1) + go func(senderIdx int) { + defer wg.Done() + + // Child (sender in reverse) at indices 2^(iter-1)..2^iter-1, + // parent at index senderIdx (0..2^(iter-1)-1). + childNodeIdx := int(math.Pow(2, float64(iter-1))) + senderIdx + parentNodeIdx := senderIdx + if childNodeIdx >= totalNodes { + return + } + + t := time.Now() + _, err := wallets[childNodeIdx].client.Transfer(ctx, wallets[parentNodeIdx].addr, asset, transferAmount) + iterResults[senderIdx] = Result{Duration: time.Since(t), Err: err} + if err != nil { + fmt.Fprintf(os.Stderr, "\n Reverse transfer failed at iteration %d, sender %d: %v\n", iter, senderIdx, err) + } + }(s) + } + wg.Wait() + + // Close children after reverse iteration — they won't be needed again. + layerStart := int(math.Pow(2, float64(iter-1))) + layerEnd := int(math.Pow(2, float64(iter))) - 1 + if layerEnd >= totalNodes { + layerEnd = totalNodes - 1 + } + for i := layerStart; i <= layerEnd; i++ { + if wallets[i].client != nil { + wallets[i].client.Close() + wallets[i].client = nil + } + } + + if failed := collectErrors(iterResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: transfer failed during reverse phase: %v\n", e) + } + printStormReport("storm-transfers", results, time.Since(start)) + return 1 + } + results = append(results, iterResults...) + } + + totalTime := time.Since(start) + fmt.Println(" Cleanup finished.") + printStormReport("storm-transfers", results, totalTime) + + fmt.Println("PASS") + return 0 +} + +func collectErrors(results []Result) []error { + var errs []error + for _, r := range results { + if r.Err != nil { + errs = append(errs, r.Err) + } + } + return errs +} + +func printStormReport(method string, results []Result, totalTime time.Duration) { + report := ComputeReport(method, len(results), 0, results, totalTime) + PrintReport(report) +} + +// stormNode holds a wallet with its pre-computed app session signer. +type stormNode struct { + wallet + signer *app.AppSessionSignerV1 +} + +// runSessionStorm implements the ternary-growth app session storm. +// +// Spec: sessions:iterations:cycles:asset:amount +// +// Tree growth: each existing node opens a 3-participant session with 2 new children per iteration. +// Total nodes = 3^iterations. Indexing: at iteration i, parent p's children are at +// 3^(i-1) + 2*p and 3^(i-1) + 2*p + 1. +// +// Forward: parent deposits, reallocates to children, close. +// Plateau: last-layer sessions bounce back and forth for N cycles. +// Reverse: children deposit back, reallocate to parent, close. +func runSessionStorm(parts []string) int { + if len(parts) < 4 { + fmt.Fprintf(os.Stderr, "ERROR: sessions requires iterations, cycles, asset, and amount\n") + fmt.Fprintf(os.Stderr, "Usage: nitronode stress-test storm sessions::::\n") + return 1 + } + + iterations, err := strconv.Atoi(parts[0]) + if err != nil || iterations <= 0 { + fmt.Fprintf(os.Stderr, "ERROR: invalid iterations %q: must be positive integer\n", parts[0]) + return 1 + } + + cycles, err := strconv.Atoi(parts[1]) + if err != nil || cycles < 0 { + fmt.Fprintf(os.Stderr, "ERROR: invalid cycles %q: must be non-negative integer\n", parts[1]) + return 1 + } + + asset := parts[2] + + amount, err := decimal.NewFromString(parts[3]) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: invalid amount %q: %v\n", parts[3], err) + return 1 + } + + if os.Getenv("STRESS_PRIVATE_KEY") == "" { + fmt.Fprintf(os.Stderr, "ERROR: STRESS_PRIVATE_KEY is required for storm sessions (origin must have funds)\n") + return 1 + } + + cfg, err := ReadConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + return 1 + } + + originAddr, err := cfg.WalletAddress() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + return 1 + } + + totalNodes := int(math.Pow(3, float64(iterations))) + derivedNodes := totalNodes - 1 + // Forward: 4 ops per session (create + deposit + reallocate + close) + // Reverse: 5 ops per session (create + deposit_child1 + deposit_child2 + reallocate + close) + forwardSessionsTotal := (totalNodes - 1) / 2 // sum of 3^(i-1) for i=1..iterations + reverseSessionsTotal := forwardSessionsTotal + lastLayerParents := int(math.Pow(3, float64(iterations-1))) + // Plateau: each cycle = back (5 ops/session) + forth (4 ops/session) for lastLayerParents sessions + plateauSessions := cycles * 2 * lastLayerParents + plateauOps := cycles * lastLayerParents * (5 + 4) + forwardOps := forwardSessionsTotal * 4 + reverseOps := reverseSessionsTotal * 5 + totalOps := forwardOps + plateauOps + reverseOps + originAmount := amount.Mul(decimal.NewFromInt(int64(totalNodes))) + + fmt.Printf("Session Storm\n") + fmt.Printf(" Origin: %s\n", originAddr) + fmt.Printf(" Iterations: %d\n", iterations) + fmt.Printf(" Cycles: %d\n", cycles) + fmt.Printf(" Asset: %s\n", asset) + fmt.Printf(" Amount/leaf: %s\n", amount.String()) + fmt.Printf(" Origin needs: %s %s\n", originAmount.String(), asset) + fmt.Printf(" Wallets: %d (derived)\n", derivedNodes) + fmt.Printf(" Sessions: %d forward + %d plateau + %d reverse\n", forwardSessionsTotal, plateauSessions, reverseSessionsTotal) + fmt.Printf(" Operations: %d total (%d forward + %d plateau + %d reverse)\n", totalOps, forwardOps, plateauOps, reverseOps) + + ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout) + defer cancel() + + // Derive all nodes upfront (cheap, no connections). + fmt.Printf(" Deriving %d wallets and signers...\n", derivedNodes) + nodes := make([]stormNode, totalNodes) + + // Origin at index 0. + originSigner, err := createAppSessionSigner(cfg.PrivateKey) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to create origin signer: %v\n", err) + return 1 + } + nodes[0] = stormNode{ + wallet: wallet{key: cfg.PrivateKey, addr: originAddr}, + signer: originSigner, + } + + for i := 1; i < totalNodes; i++ { + key := deriveKey(cfg.PrivateKey, i-1) + addr, err := walletAddressFromKey(key) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to derive wallet %d: %v\n", i, err) + return 1 + } + signer, err := createAppSessionSigner(key) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to create signer for wallet %d: %v\n", i, err) + return 1 + } + nodes[i] = stormNode{ + wallet: wallet{key: key, addr: addr}, + signer: signer, + } + } + + // Connect origin. + fmt.Println(" Connecting origin...") + originClient, err := createClient(cfg.WsURL, cfg.PrivateKey) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to connect origin: %v\n", err) + return 1 + } + nodes[0].client = originClient + defer func() { + for i := range nodes { + if nodes[i].client != nil { + nodes[i].client.Close() + } + } + }() + + var results []Result + appID := fmt.Sprintf("stress-storm-%d", time.Now().UnixNano()) + fmt.Printf(" App ID: %s\n", appID) + start := time.Now() + + // Forward phase. + for iter := 1; iter <= iterations; iter++ { + parentsCount := int(math.Pow(3, float64(iter-1))) + layerBase := parentsCount // first child index for this iteration + depositAmount := amount.Mul(decimal.NewFromInt(2)).Mul(decimal.NewFromInt(int64(math.Pow(3, float64(iterations-iter))))) + reallocAmount := amount.Mul(decimal.NewFromInt(int64(math.Pow(3, float64(iterations-iter))))) + + // Connect children for this iteration. + childStart := layerBase + childEnd := layerBase + 2*parentsCount - 1 + fmt.Printf(" Connecting wallets %d..%d for forward iteration %d...\n", childStart, childEnd, iter) + for i := childStart; i <= childEnd; i++ { + c, err := createClient(cfg.WsURL, nodes[i].key) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to connect wallet %d: %v\n", i, err) + return 1 + } + nodes[i].client = c + time.Sleep(10 * time.Millisecond) + } + + // Parents that were children in the previous iteration need to + // acknowledge to open a channel before they can deposit. + // Previous iteration's children: indices prevLayerBase..layerBase-1. + if iter >= 2 { + prevLayerBase := int(math.Pow(3, float64(iter-2))) + fmt.Printf(" Acknowledging wallets %d..%d...\n", prevLayerBase, layerBase-1) + for i := prevLayerBase; i < layerBase; i++ { + if _, err := nodes[i].client.Acknowledge(ctx, asset); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: acknowledge wallet %d: %v\n", i, err) + return 1 + } + } + } + + fmt.Printf(" Forward iteration %d/%d: %d sessions, deposit %s, reallocate %s each\n", + iter, iterations, parentsCount, depositAmount.String(), reallocAmount.String()) + + iterResults := make([]Result, parentsCount*4) + var wg sync.WaitGroup + + for s := range parentsCount { + if ctx.Err() != nil { + fmt.Fprintf(os.Stderr, "\nERROR: context cancelled: %v\n", ctx.Err()) + return 1 + } + + wg.Add(1) + go func(sessionIdx int) { + defer wg.Done() + parentIdx := sessionIdx + child1Idx := layerBase + 2*sessionIdx + child2Idx := layerBase + 2*sessionIdx + 1 + base := sessionIdx * 4 + + nonce := uint64(iter)*10000 + uint64(sessionIdx) + results := executeForwardSession(ctx, nodes, parentIdx, child1Idx, child2Idx, + asset, depositAmount, reallocAmount, appID, nonce) + copy(iterResults[base:base+4], results) + }(s) + } + wg.Wait() + + if failed := collectErrors(iterResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: session failed during forward phase: %v\n", e) + } + printStormReport("storm-sessions", results, time.Since(start)) + return 1 + } + results = append(results, iterResults...) + } + + // One-time acknowledge before plateau/reverse: all last-layer wallets + // that will need to deposit. Channels stay open once acknowledged. + // - Leaf children received funds from last forward close. + // - Non-origin parents need channels for plateau forth deposits. + { + layerBase := int(math.Pow(3, float64(iterations-1))) + childStart := layerBase + childEnd := layerBase + 2*lastLayerParents - 1 + fmt.Printf(" Acknowledging leaf wallets %d..%d...\n", childStart, childEnd) + for i := childStart; i <= childEnd; i++ { + if _, err := nodes[i].client.Acknowledge(ctx, asset); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: acknowledge wallet %d: %v\n", i, err) + return 1 + } + } + } + + // Plateau phase: last-layer sessions bounce back and forth. + if cycles > 0 { + layerBase := int(math.Pow(3, float64(iterations-1))) + childAmount := amount // leaf amount + depositAmount := amount.Mul(decimal.NewFromInt(2)) + + fmt.Printf(" Plateau phase: %d cycles of %d back-and-forth sessions...\n", cycles, lastLayerParents) + for c := 1; c <= cycles; c++ { + // Back: children deposit, reallocate to parent, close (5 ops each). + fmt.Printf(" Plateau %d back: %d sessions, children deposit %s each\n", + c, lastLayerParents, childAmount.String()) + backResults := make([]Result, lastLayerParents*5) + var wg sync.WaitGroup + for s := range lastLayerParents { + wg.Add(1) + go func(sessionIdx int) { + defer wg.Done() + parentIdx := sessionIdx + child1Idx := layerBase + 2*sessionIdx + child2Idx := layerBase + 2*sessionIdx + 1 + base := sessionIdx * 5 + nonce := uint64(100+c)*10000 + uint64(sessionIdx) + r := executeReverseSession(ctx, nodes, parentIdx, child1Idx, child2Idx, + asset, childAmount, depositAmount, appID, nonce) + copy(backResults[base:base+5], r) + }(s) + } + wg.Wait() + if failed := collectErrors(backResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: session failed during plateau back: %v\n", e) + } + printStormReport("storm-sessions", results, time.Since(start)) + return 1 + } + results = append(results, backResults...) + + // Forth: parent deposits, reallocates to children, close (4 ops each). + fmt.Printf(" Plateau %d forth: %d sessions, parent deposits %s each\n", + c, lastLayerParents, depositAmount.String()) + forthResults := make([]Result, lastLayerParents*4) + for s := range lastLayerParents { + wg.Add(1) + go func(sessionIdx int) { + defer wg.Done() + parentIdx := sessionIdx + child1Idx := layerBase + 2*sessionIdx + child2Idx := layerBase + 2*sessionIdx + 1 + base := sessionIdx * 4 + nonce := uint64(200+c)*10000 + uint64(sessionIdx) + r := executeForwardSession(ctx, nodes, parentIdx, child1Idx, child2Idx, + asset, depositAmount, childAmount, appID, nonce) + copy(forthResults[base:base+4], r) + }(s) + } + wg.Wait() + if failed := collectErrors(forthResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: session failed during plateau forth: %v\n", e) + } + printStormReport("storm-sessions", results, time.Since(start)) + return 1 + } + results = append(results, forthResults...) + } + } + + fmt.Println(" Starting reverse (cleanup)...") + + // Reverse phase. + for iter := iterations; iter >= 1; iter-- { + parentsCount := int(math.Pow(3, float64(iter-1))) + layerBase := parentsCount + childAmount := amount.Mul(decimal.NewFromInt(int64(math.Pow(3, float64(iterations-iter))))) + collectAmount := childAmount.Mul(decimal.NewFromInt(2)) + + fmt.Printf(" Reverse iteration %d/%d: %d sessions, children deposit %s each\n", + iter, iterations, parentsCount, childAmount.String()) + + iterResults := make([]Result, parentsCount*5) + var wg sync.WaitGroup + + for s := range parentsCount { + if ctx.Err() != nil { + fmt.Fprintf(os.Stderr, "\nERROR: context cancelled: %v\n", ctx.Err()) + return 1 + } + + wg.Add(1) + go func(sessionIdx int) { + defer wg.Done() + parentIdx := sessionIdx + child1Idx := layerBase + 2*sessionIdx + child2Idx := layerBase + 2*sessionIdx + 1 + base := sessionIdx * 5 + + nonce := uint64(iterations+iter)*10000 + uint64(sessionIdx) + results := executeReverseSession(ctx, nodes, parentIdx, child1Idx, child2Idx, + asset, childAmount, collectAmount, appID, nonce) + copy(iterResults[base:base+5], results) + }(s) + } + wg.Wait() + + // Close children after reverse — they won't be needed again. + childStart := layerBase + childEnd := layerBase + 2*parentsCount - 1 + for i := childStart; i <= childEnd; i++ { + if nodes[i].client != nil { + nodes[i].client.Close() + nodes[i].client = nil + } + } + + if failed := collectErrors(iterResults); len(failed) > 0 { + for _, e := range failed { + fmt.Fprintf(os.Stderr, "ERROR: session failed during reverse phase: %v\n", e) + } + printStormReport("storm-sessions", results, time.Since(start)) + return 1 + } + results = append(results, iterResults...) + } + + totalTime := time.Since(start) + fmt.Println(" Cleanup finished.") + printStormReport("storm-sessions", results, totalTime) + + fmt.Println("PASS") + return 0 +} + +// executeForwardSession runs a single forward app session lifecycle: +// acknowledge (depositor) → create → deposit (parent) → reallocate (parent → children) → close. +// Returns 4 Results (acknowledge is not measured, it's setup). +func executeForwardSession( + ctx context.Context, + nodes []stormNode, + parentIdx, child1Idx, child2Idx int, + asset string, + depositAmount, reallocAmount decimal.Decimal, + appID string, nonce uint64, +) []Result { + results := make([]Result, 4) + parent := &nodes[parentIdx] + child1 := &nodes[child1Idx] + child2 := &nodes[child2Idx] + + definition := app.AppDefinitionV1{ + ApplicationID: appID, + Participants: []app.AppParticipantV1{ + {WalletAddress: parent.addr, SignatureWeight: 1}, + {WalletAddress: child1.addr, SignatureWeight: 1}, + {WalletAddress: child2.addr, SignatureWeight: 1}, + }, + Quorum: 3, + Nonce: nonce, + } + + signers := []*app.AppSessionSignerV1{parent.signer, child1.signer, child2.signer} + + // 1. Create + createReq, err := app.PackCreateAppSessionRequestV1(definition, "{}") + if err != nil { + results[0] = Result{Err: fmt.Errorf("pack create: %w", err)} + skipResults(results, 1, results[0].Err) + return results + } + createSigs, err := stormSignAll(signers, createReq) + if err != nil { + results[0] = Result{Err: fmt.Errorf("sign create: %w", err)} + skipResults(results, 1, results[0].Err) + return results + } + + t := time.Now() + sessionID, _, _, err := parent.client.CreateAppSession(ctx, definition, "{}", createSigs) + results[0] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 1, err) + return results + } + + // 2. Deposit (parent deposits) + version := uint64(2) + depositUpdate := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: parent.addr, Asset: asset, Amount: depositAmount}, + }, + } + depositReq, _ := app.PackAppStateUpdateV1(depositUpdate) + depositSigs, _ := stormSignAll(signers, depositReq) + + t = time.Now() + _, err = parent.client.SubmitAppSessionDeposit(ctx, depositUpdate, depositSigs, asset, depositAmount) + results[1] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 2, err) + return results + } + version++ + + // 3. Reallocate (parent → children) + reallocUpdate := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: parent.addr, Asset: asset, Amount: decimal.Zero}, + {Participant: child1.addr, Asset: asset, Amount: reallocAmount}, + {Participant: child2.addr, Asset: asset, Amount: reallocAmount}, + }, + } + reallocReq, _ := app.PackAppStateUpdateV1(reallocUpdate) + reallocSigs, _ := stormSignAll(signers, reallocReq) + + t = time.Now() + err = parent.client.SubmitAppState(ctx, reallocUpdate, reallocSigs) + results[2] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 3, err) + return results + } + version++ + + // 4. Close + closeUpdate := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentClose, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: parent.addr, Asset: asset, Amount: decimal.Zero}, + {Participant: child1.addr, Asset: asset, Amount: reallocAmount}, + {Participant: child2.addr, Asset: asset, Amount: reallocAmount}, + }, + } + closeReq, _ := app.PackAppStateUpdateV1(closeUpdate) + closeSigs, _ := stormSignAll(signers, closeReq) + + t = time.Now() + err = parent.client.SubmitAppState(ctx, closeUpdate, closeSigs) + results[3] = Result{Duration: time.Since(t), Err: err} + return results +} + +// executeReverseSession runs a single reverse app session lifecycle: +// create → deposit (child1) → deposit (child2) → reallocate (children → parent) → close. +// Returns 5 Results. +func executeReverseSession( + ctx context.Context, + nodes []stormNode, + parentIdx, child1Idx, child2Idx int, + asset string, + childAmount, collectAmount decimal.Decimal, + appID string, nonce uint64, +) []Result { + results := make([]Result, 5) + parent := &nodes[parentIdx] + child1 := &nodes[child1Idx] + child2 := &nodes[child2Idx] + + definition := app.AppDefinitionV1{ + ApplicationID: appID, + Participants: []app.AppParticipantV1{ + {WalletAddress: parent.addr, SignatureWeight: 1}, + {WalletAddress: child1.addr, SignatureWeight: 1}, + {WalletAddress: child2.addr, SignatureWeight: 1}, + }, + Quorum: 3, + Nonce: nonce, + } + + signers := []*app.AppSessionSignerV1{parent.signer, child1.signer, child2.signer} + + // 1. Create + createReq, err := app.PackCreateAppSessionRequestV1(definition, "{}") + if err != nil { + results[0] = Result{Err: fmt.Errorf("pack create: %w", err)} + skipResults(results, 1, results[0].Err) + return results + } + createSigs, err := stormSignAll(signers, createReq) + if err != nil { + results[0] = Result{Err: fmt.Errorf("sign create: %w", err)} + skipResults(results, 1, results[0].Err) + return results + } + + t := time.Now() + sessionID, _, _, err := parent.client.CreateAppSession(ctx, definition, "{}", createSigs) + results[0] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 1, err) + return results + } + + // 2. Deposit child1 + version := uint64(2) + deposit1Update := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: child1.addr, Asset: asset, Amount: childAmount}, + }, + } + deposit1Req, _ := app.PackAppStateUpdateV1(deposit1Update) + deposit1Sigs, _ := stormSignAll(signers, deposit1Req) + + t = time.Now() + _, err = child1.client.SubmitAppSessionDeposit(ctx, deposit1Update, deposit1Sigs, asset, childAmount) + results[1] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 2, err) + return results + } + version++ + + // 3. Deposit child2 + deposit2Update := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: child2.addr, Asset: asset, Amount: childAmount}, + }, + } + deposit2Req, _ := app.PackAppStateUpdateV1(deposit2Update) + deposit2Sigs, _ := stormSignAll(signers, deposit2Req) + + t = time.Now() + _, err = child2.client.SubmitAppSessionDeposit(ctx, deposit2Update, deposit2Sigs, asset, childAmount) + results[2] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 3, err) + return results + } + version++ + + // 4. Reallocate (children → parent) + reallocUpdate := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: parent.addr, Asset: asset, Amount: collectAmount}, + {Participant: child1.addr, Asset: asset, Amount: decimal.Zero}, + {Participant: child2.addr, Asset: asset, Amount: decimal.Zero}, + }, + } + reallocReq, _ := app.PackAppStateUpdateV1(reallocUpdate) + reallocSigs, _ := stormSignAll(signers, reallocReq) + + t = time.Now() + err = parent.client.SubmitAppState(ctx, reallocUpdate, reallocSigs) + results[3] = Result{Duration: time.Since(t), Err: err} + if err != nil { + skipResults(results, 4, err) + return results + } + version++ + + // 5. Close + closeUpdate := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentClose, + Version: version, + Allocations: []app.AppAllocationV1{ + {Participant: parent.addr, Asset: asset, Amount: collectAmount}, + {Participant: child1.addr, Asset: asset, Amount: decimal.Zero}, + {Participant: child2.addr, Asset: asset, Amount: decimal.Zero}, + }, + } + closeReq, _ := app.PackAppStateUpdateV1(closeUpdate) + closeSigs, _ := stormSignAll(signers, closeReq) + + t = time.Now() + err = parent.client.SubmitAppState(ctx, closeUpdate, closeSigs) + results[4] = Result{Duration: time.Since(t), Err: err} + return results +} + +func createAppSessionSigner(privateKey string) (*app.AppSessionSignerV1, error) { + msgSigner, err := sign.NewEthereumMsgSigner(privateKey) + if err != nil { + return nil, fmt.Errorf("failed to create msg signer: %w", err) + } + return app.NewAppSessionWalletSignerV1(msgSigner) +} + +func stormSignAll(signers []*app.AppSessionSignerV1, data []byte) ([]string, error) { + sigs := make([]string, len(signers)) + for i, s := range signers { + sig, err := s.Sign(data) + if err != nil { + return nil, fmt.Errorf("signer %d: %w", i, err) + } + sigs[i] = sig.String() + } + return sigs, nil +} + +func skipResults(results []Result, from int, cause error) { + for i := from; i < len(results); i++ { + results[i] = Result{Err: fmt.Errorf("skipped: %w", cause)} + } +} + +func printStormUsage() { + fmt.Println("Usage: nitronode stress-test storm :") + fmt.Println() + fmt.Println("Available methods:") + fmt.Println() + fmt.Println(" transfers:iterations:cycles:asset:amount") + fmt.Println(" Binary-tree transfer storm. Each iteration doubles active wallets.") + fmt.Println(" After iterations, plateau cycles bounce last-layer transfers back and forth.") + fmt.Println(" Origin wallet needs amount * 2^iterations of the asset.") + fmt.Println() + fmt.Println(" Example: nitronode stress-test storm transfers:3:2:usdc:1") + fmt.Println(" Iteration 1: A -> B (4 usdc)") + fmt.Println(" Iteration 2: A -> C (2), B -> D (2)") + fmt.Println(" Iteration 3: A -> E (1), B -> F (1), C -> G (1), D -> H (1)") + fmt.Println(" Plateau 1 back: E -> A, F -> B, G -> C, H -> D") + fmt.Println(" Plateau 1 forth: A -> E, B -> F, C -> G, D -> H") + fmt.Println(" Plateau 2 back: E -> A, F -> B, G -> C, H -> D") + fmt.Println(" Plateau 2 forth: A -> E, B -> F, C -> G, D -> H") + fmt.Println(" Reverse: E -> A, F -> B, G -> C, H -> D, C -> A, D -> B, B -> A") + fmt.Println() + fmt.Println(" sessions:iterations:cycles:asset:amount") + fmt.Println(" Ternary-growth app session storm. Each iteration triples active wallets.") + fmt.Println(" After iterations, plateau cycles bounce last-layer sessions back and forth.") + fmt.Println(" Origin wallet needs amount * 3^iterations of the asset.") + fmt.Println() + fmt.Println(" Example: nitronode stress-test storm sessions:2:2:usdc:1") + fmt.Println(" Iteration 1: session(A,B,C) — A deposits 6, reallocates 3 to B, 3 to C") + fmt.Println(" Iteration 2: session(A,D,E), session(B,F,G), session(C,H,I) — each deposits 2, reallocates 1 each") + fmt.Println(" Plateau 1 back: D,E -> A; F,G -> B; H,I -> C") + fmt.Println(" Plateau 1 forth: A -> D,E; B -> F,G; C -> H,I") + fmt.Println(" Plateau 2 back: D,E -> A; F,G -> B; H,I -> C") + fmt.Println(" Plateau 2 forth: A -> D,E; B -> F,G; C -> H,I") + fmt.Println(" Reverse 2: D,E -> A; F,G -> B; H,I -> C") + fmt.Println(" Reverse 1: B,C -> A") +} diff --git a/clearnode/stress/transfer.go b/nitronode/stress/transfer.go similarity index 100% rename from clearnode/stress/transfer.go rename to nitronode/stress/transfer.go diff --git a/clearnode/stress/types.go b/nitronode/stress/types.go similarity index 100% rename from clearnode/stress/types.go rename to nitronode/stress/types.go diff --git a/pkg/app/app_session_v1.go b/pkg/app/app_session_v1.go index f83df6277..b65c578d4 100644 --- a/pkg/app/app_session_v1.go +++ b/pkg/app/app_session_v1.go @@ -23,6 +23,17 @@ const ( AppStateUpdateIntentRebalance ) +// AllAppStateUpdateIntents enumerates every defined intent. Kept beside the +// const block so adding a new intent here is the natural place to update +// consumers that iterate the full domain (metrics seeding, drift tests). +var AllAppStateUpdateIntents = []AppStateUpdateIntent{ + AppStateUpdateIntentOperate, + AppStateUpdateIntentDeposit, + AppStateUpdateIntentWithdraw, + AppStateUpdateIntentClose, + AppStateUpdateIntentRebalance, +} + func (intent AppStateUpdateIntent) String() string { switch intent { case AppStateUpdateIntentOperate: diff --git a/pkg/app/app_v1.go b/pkg/app/app_v1.go index 08ce87afd..55324bf7b 100644 --- a/pkg/app/app_v1.go +++ b/pkg/app/app_v1.go @@ -8,10 +8,23 @@ import ( "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" ) var AppIDV1Regex = regexp.MustCompile(`^[a-z0-9][-a-z0-9]{0,65}$`) +// ApplicationIDRegex bounds the advisory application identifier to lowercase +// letters, digits, dashes and underscores, 1..66 chars — matching the DB +// column width (VARCHAR(66), see +// nitronode/config/migrations/postgres/20260420000000_add_application_id_to_writes.sql). +var ApplicationIDRegex = regexp.MustCompile(`^[a-z0-9_-]{1,66}$`) + +// IsValidApplicationID reports whether id is a well-formed advisory +// application identifier (see ApplicationIDRegex). +func IsValidApplicationID(id string) bool { + return ApplicationIDRegex.MatchString(id) +} + // AppV1 represents an application registry entry. type AppV1 struct { ID string @@ -30,8 +43,10 @@ type AppInfoV1 struct { // 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) + var err error + app.OwnerWallet, err = core.NormalizeHexAddress(app.OwnerWallet) + if err != nil { + return nil, fmt.Errorf("invalid owner wallet address: %v", err) } args := abi.Arguments{ diff --git a/pkg/app/app_v1_test.go b/pkg/app/app_v1_test.go new file mode 100644 index 000000000..244596cb9 --- /dev/null +++ b/pkg/app/app_v1_test.go @@ -0,0 +1,41 @@ +package app + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsValidApplicationID(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + id string + want bool + }{ + {"simple", "my-app", true}, + {"underscore", "my_app", true}, + {"digits", "app-123", true}, + {"dash only", "-", true}, + {"underscore only", "_", true}, + {"single char", "a", true}, + {"exactly 66 chars", strings.Repeat("a", 66), true}, + + {"empty", "", false}, + {"67 chars", strings.Repeat("a", 67), false}, + {"uppercase", "MyApp", false}, + {"space", "my app", false}, + {"dot", "my.app", false}, + {"slash", "my/app", false}, + {"newline", "my\napp", false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, IsValidApplicationID(tc.id)) + }) + } +} diff --git a/pkg/app/session_key_v1.go b/pkg/app/session_key_v1.go index 15db89adf..f377ed71c 100644 --- a/pkg/app/session_key_v1.go +++ b/pkg/app/session_key_v1.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -28,6 +29,9 @@ type AppSessionKeyStateV1 struct { ExpiresAt time.Time // UserSig is the user's signature over the session key metadata to authorize the registration/update of the session key UserSig string + // SessionKeySig is the session-key holder's signature over the same packed state. + // Required at submit time so that nobody can register a session key they do not control. + SessionKeySig string } // GenerateSessionKeyStateIDV1 generates a deterministic ID from user_address, session_key, and version. @@ -50,6 +54,54 @@ func GenerateSessionKeyStateIDV1(userAddress, sessionKey string, version uint64) return crypto.Keccak256Hash(packed).Hex(), nil } +// ValidateAppSessionKeyStateV1 verifies both signatures over the registration payload: +// UserSig must recover to state.UserAddress (wallet authorizes the delegation) and +// SessionKeySig must recover to state.SessionKey (session-key holder proves possession). +// Both signatures sign the same PackAppSessionKeyStateV1(state) payload, which already binds +// user_address and session_key — so a signature minted for one (wallet, session_key) pair +// cannot be replayed for another. +func ValidateAppSessionKeyStateV1(state AppSessionKeyStateV1) error { + if state.SessionKeySig == "" { + return fmt.Errorf("session_key_sig is required") + } + + packed, err := PackAppSessionKeyStateV1(state) + if err != nil { + return fmt.Errorf("failed to pack session key state: %w", err) + } + + recoverer, err := sign.NewAddressRecoverer(sign.TypeEthereumMsg) + if err != nil { + return fmt.Errorf("failed to create address recoverer: %w", err) + } + + userSigBytes, err := hexutil.Decode(state.UserSig) + if err != nil { + return fmt.Errorf("failed to decode user_sig: %w", err) + } + recoveredUser, err := recoverer.RecoverAddress(packed, userSigBytes) + if err != nil { + return fmt.Errorf("failed to recover user_sig: %w", err) + } + if !strings.EqualFold(recoveredUser.String(), state.UserAddress) { + return fmt.Errorf("user_sig does not match user_address") + } + + sessionKeySigBytes, err := hexutil.Decode(state.SessionKeySig) + if err != nil { + return fmt.Errorf("failed to decode session_key_sig: %w", err) + } + recoveredKey, err := recoverer.RecoverAddress(packed, sessionKeySigBytes) + if err != nil { + return fmt.Errorf("failed to recover session_key_sig: %w", err) + } + if !strings.EqualFold(recoveredKey.String(), state.SessionKey) { + return fmt.Errorf("session_key_sig does not match session_key") + } + + return nil +} + // PackAppSessionKeyStateV1 packs the session key state for signing using ABI encoding. // This is used to generate a deterministic hash that the user signs when registering/updating a session key. // The user_sig field is excluded from packing since it is the signature itself. @@ -70,12 +122,12 @@ func PackAppSessionKeyStateV1(state AppSessionKeyStateV1) ([]byte, error) { applicationIDHashes := make([][32]byte, len(state.ApplicationIDs)) for i, id := range state.ApplicationIDs { - applicationIDHashes[i] = common.HexToHash(id) + applicationIDHashes[i] = crypto.Keccak256Hash([]byte(id)) } appSessionIDHashes := make([][32]byte, len(state.AppSessionIDs)) for i, id := range state.AppSessionIDs { - appSessionIDHashes[i] = common.HexToHash(id) + appSessionIDHashes[i] = crypto.Keccak256Hash([]byte(id)) } packed, err := args.Pack( diff --git a/pkg/app/session_key_v1_test.go b/pkg/app/session_key_v1_test.go index b01748b24..ef3602890 100644 --- a/pkg/app/session_key_v1_test.go +++ b/pkg/app/session_key_v1_test.go @@ -47,6 +47,89 @@ func TestGenerateSessionKeyStateIDV1(t *testing.T) { assert.NotEqual(t, id1, id3) } +func TestValidateAppSessionKeyStateV1(t *testing.T) { + t.Parallel() + userSigner, userAddress := createTestSigner(t) + sessionSigner, sessionKeyAddr := createTestSigner(t) + + version := uint64(1) + appSessionIDs := []string{ + "0x1111111111111111111111111111111111111111111111111111111111111111", + } + applicationIDs := []string{ + "0x2222222222222222222222222222222222222222222222222222222222222222", + } + expiresAt := time.Now().Add(1 * time.Hour) + + baseState := AppSessionKeyStateV1{ + UserAddress: userAddress, + SessionKey: sessionKeyAddr, + Version: version, + AppSessionIDs: appSessionIDs, + ApplicationIDs: applicationIDs, + ExpiresAt: expiresAt, + } + + packed, err := PackAppSessionKeyStateV1(baseState) + require.NoError(t, err) + + userSig, err := userSigner.Sign(packed) + require.NoError(t, err) + sessionKeySig, err := sessionSigner.Sign(packed) + require.NoError(t, err) + + state := baseState + state.UserSig = hexutil.Encode(userSig) + state.SessionKeySig = hexutil.Encode(sessionKeySig) + + require.NoError(t, ValidateAppSessionKeyStateV1(state)) + + // Empty session_key_sig + stateNoKeySig := state + stateNoKeySig.SessionKeySig = "" + err = ValidateAppSessionKeyStateV1(stateNoKeySig) + require.Error(t, err) + assert.Contains(t, err.Error(), "session_key_sig is required") + + // user_sig signed by wrong wallet + wrongSigner, _ := createTestSigner(t) + wrongUserSig, err := wrongSigner.Sign(packed) + require.NoError(t, err) + stateWrongUser := state + stateWrongUser.UserSig = hexutil.Encode(wrongUserSig) + err = ValidateAppSessionKeyStateV1(stateWrongUser) + require.Error(t, err) + assert.Contains(t, err.Error(), "user_sig does not match user_address") + + // session_key_sig signed by wrong key + wrongKeySigner, _ := createTestSigner(t) + wrongKeySig, err := wrongKeySigner.Sign(packed) + require.NoError(t, err) + stateWrongKey := state + stateWrongKey.SessionKeySig = hexutil.Encode(wrongKeySig) + err = ValidateAppSessionKeyStateV1(stateWrongKey) + require.Error(t, err) + assert.Contains(t, err.Error(), "session_key_sig does not match session_key") + + // Tampered version (hash mismatch on recover) + stateTampered := state + stateTampered.Version = 2 + assert.Error(t, ValidateAppSessionKeyStateV1(stateTampered)) + + // Cross-wallet replay: substitute a different user_address. Packed bytes diverge so + // neither recovery yields the matching address. + _, otherUser := createTestSigner(t) + stateCrossUser := state + stateCrossUser.UserAddress = otherUser + assert.Error(t, ValidateAppSessionKeyStateV1(stateCrossUser)) + + // Cross-session-key replay: substitute a different session_key. + _, otherKey := createTestSigner(t) + stateCrossKey := state + stateCrossKey.SessionKey = otherKey + assert.Error(t, ValidateAppSessionKeyStateV1(stateCrossKey)) +} + func TestPackAppSessionKeyStateV1(t *testing.T) { t.Parallel() expiresAt := time.Unix(1739812234, 0) @@ -67,10 +150,60 @@ func TestPackAppSessionKeyStateV1(t *testing.T) { // Strengthen assertion: validate content by comparing against pre-calculated hash // This ensures that if the packing logic changes, the test will fail. - expectedHash := "0x9fedfbcd577c5e677b95b1273e38f52ffdeee096e98f731c5455e4c73e0274aa" + expectedHash := "0x6d404fa628918dbe4abec4ae2808c7ea01dc880ad4ad392ca2d0c4ce21f706c1" assert.Equal(t, expectedHash, hexutil.Encode(packed)) } +func TestPackAppSessionKeyStateV1_NoIDCollision(t *testing.T) { + t.Parallel() + base := AppSessionKeyStateV1{ + UserAddress: "0x1111111111111111111111111111111111111111", + SessionKey: "0x2222222222222222222222222222222222222222", + Version: 1, + ExpiresAt: time.Unix(1739812234, 0), + } + + // Human-readable (non-hex) IDs must each produce a distinct packed hash. + ids := []string{"app-1", "app-2", "trading", "gaming", "defi"} + hashes := make(map[string]string) + for _, id := range ids { + s := base + s.ApplicationIDs = []string{id} + packed, err := PackAppSessionKeyStateV1(s) + require.NoError(t, err) + h := hexutil.Encode(packed) + if prev, seen := hashes[h]; seen { + t.Fatalf("collision: %q and %q produced the same hash %s", prev, id, h) + } + hashes[h] = id + } + + // Same uniqueness requirement holds for AppSessionIDs. + sessionHashes := make(map[string]string) + for _, id := range ids { + s := base + s.AppSessionIDs = []string{id} + packed, err := PackAppSessionKeyStateV1(s) + require.NoError(t, err) + h := hexutil.Encode(packed) + if prev, seen := sessionHashes[h]; seen { + t.Fatalf("appSessionID collision: %q and %q produced the same hash %s", prev, id, h) + } + sessionHashes[h] = id + } + + // An empty ID and a whitespace ID must also be distinct. + sEmpty := base + sEmpty.ApplicationIDs = []string{""} + sSpace := base + sSpace.ApplicationIDs = []string{" "} + hashEmpty, err := PackAppSessionKeyStateV1(sEmpty) + require.NoError(t, err) + hashSpace, err := PackAppSessionKeyStateV1(sSpace) + require.NoError(t, err) + assert.NotEqual(t, hexutil.Encode(hashEmpty), hexutil.Encode(hashSpace)) +} + func TestAppSessionSignerV1(t *testing.T) { t.Parallel() baseSigner, _ := createTestSigner(t) diff --git a/pkg/blockchain/evm/blockchain_client.go b/pkg/blockchain/evm/blockchain_client.go index c922c354a..db95dd31b 100644 --- a/pkg/blockchain/evm/blockchain_client.go +++ b/pkg/blockchain/evm/blockchain_client.go @@ -72,31 +72,6 @@ func NewBlockchainClient( return client, nil } -// ========= Getters - IVault ========= - -func (c *BlockchainClient) GetAccountsBalances(accounts []string, tokens []string) ([][]decimal.Decimal, error) { - if len(accounts) == 0 || len(tokens) == 0 { - return [][]decimal.Decimal{}, nil - } - - result := make([][]decimal.Decimal, len(accounts)) - for i, account := range accounts { - result[i] = make([]decimal.Decimal, len(tokens)) - accountAddr := common.HexToAddress(account) - - for j, token := range tokens { - tokenAddr := common.HexToAddress(token) - balance, err := c.contract.GetAccountBalance(nil, accountAddr, tokenAddr) - if err != nil { - return nil, errors.Wrapf(err, "failed to get balance for account %s and token %s", account, token) - } - result[i][j] = decimal.NewFromBigInt(balance, 0) - } - } - - return result, nil -} - func (c *BlockchainClient) getAllowance(asset string, owner string) (decimal.Decimal, error) { tokenAddrHex, err := c.assetStore.GetTokenAddress(asset, c.blockchainID) if err != nil { @@ -167,7 +142,7 @@ func (c *BlockchainClient) GetTokenBalance(asset string, walletAddress string) ( func (c *BlockchainClient) GetNodeBalance(token string) (decimal.Decimal, error) { tokenAddr := common.HexToAddress(token) - balance, err := c.contract.GetAccountBalance(nil, c.nodeAddress, tokenAddr) + balance, err := c.contract.GetNodeBalance(nil, tokenAddr) if err != nil { return decimal.Zero, errors.Wrapf(err, "failed to get node balance for token %s", token) } @@ -237,7 +212,7 @@ func (c *BlockchainClient) GetEscrowDepositData(escrowChannelID string) (core.Es return core.EscrowDepositDataResponse{ EscrowChannelID: escrowChannelID, - Node: c.channelHubContractAddress.Hex(), + Node: c.nodeAddress.Hex(), LastState: *lastState, UnlockExpiry: data.UnlockAt, ChallengeExpiry: data.ChallengeExpiry, @@ -262,22 +237,21 @@ func (c *BlockchainClient) GetEscrowWithdrawalData(escrowChannelID string) (core return core.EscrowWithdrawalDataResponse{ EscrowChannelID: escrowChannelID, - Node: c.channelHubContractAddress.Hex(), + Node: c.nodeAddress.Hex(), LastState: *lastState, }, nil } // ========= IVault Functions ========= -func (c *BlockchainClient) Deposit(node, token string, amount decimal.Decimal) (string, error) { - nodeAddr := common.HexToAddress(node) +func (c *BlockchainClient) Deposit(token string, amount decimal.Decimal) (string, error) { tokenAddr := common.HexToAddress(token) decimals, err := c.assetStore.GetTokenDecimals(c.blockchainID, token) if err != nil { return "", errors.Wrapf(err, "failed to get token decimals for token %s", token) } - amountBig, err := core.DecimalToBigInt(amount, decimals) + amountBig, err := core.DecimalToUint256(amount, decimals) if err != nil { return "", errors.Wrapf(err, "failed to convert amount %s to big.Int", amount.String()) } @@ -291,23 +265,22 @@ func (c *BlockchainClient) Deposit(node, token string, amount decimal.Decimal) ( return "", err } - tx, err := c.contract.DepositToVault(c.transactOpts, nodeAddr, tokenAddr, amountBig) + tx, err := c.contract.DepositToNode(c.transactOpts, tokenAddr, amountBig) if err != nil { - return "", errors.Wrap(err, "failed to deposit to vault") + return "", errors.Wrap(err, "failed to deposit to node") } return tx.Hash().Hex(), nil } -func (c *BlockchainClient) Withdraw(node, token string, amount decimal.Decimal) (string, error) { - nodeAddr := common.HexToAddress(node) +func (c *BlockchainClient) Withdraw(to, token string, amount decimal.Decimal) (string, error) { tokenAddr := common.HexToAddress(token) decimals, err := c.assetStore.GetTokenDecimals(c.blockchainID, token) if err != nil { return "", errors.Wrapf(err, "failed to get token decimals for token %s", token) } - amountBig, err := core.DecimalToBigInt(amount, decimals) + amountBig, err := core.DecimalToUint256(amount, decimals) if err != nil { return "", errors.Wrapf(err, "failed to convert amount %s to big.Int", amount.String()) } @@ -316,9 +289,9 @@ func (c *BlockchainClient) Withdraw(node, token string, amount decimal.Decimal) return "", err } - tx, err := c.contract.WithdrawFromVault(c.transactOpts, nodeAddr, tokenAddr, amountBig) + tx, err := c.contract.WithdrawFromNode(c.transactOpts, common.HexToAddress(to), tokenAddr, amountBig) if err != nil { - return "", errors.Wrap(err, "failed to withdraw from vault") + return "", errors.Wrap(err, "failed to withdraw from node") } return tx.Hash().Hex(), nil @@ -342,7 +315,7 @@ func (c *BlockchainClient) Approve(asset string, amount decimal.Decimal) (string return "", errors.Wrapf(err, "failed to get token decimals for %s", asset) } - amountBig, err := core.DecimalToBigInt(amount, decimals) + amountBig, err := core.DecimalToUint256(amount, decimals) if err != nil { return "", errors.Wrapf(err, "failed to convert amount %s to big.Int", amount.String()) } @@ -402,7 +375,7 @@ func (c *BlockchainClient) Create(def core.ChannelDefinition, initCCS core.State } if contractState.HomeLedger.Token == (common.Address{}) { - value, err := core.DecimalToBigInt(initCCS.Transition.Amount, contractState.HomeLedger.Decimals) + value, err := core.DecimalToUint256(initCCS.Transition.Amount, contractState.HomeLedger.Decimals) if err != nil { return "", errors.Wrap(err, "failed to convert native deposit amount to wei") } @@ -495,7 +468,7 @@ func (c *BlockchainClient) Checkpoint(candidate core.State) (string, error) { } if contractCandidate.HomeLedger.Token == (common.Address{}) { - value, valueErr := core.DecimalToBigInt(candidate.Transition.Amount, contractCandidate.HomeLedger.Decimals) + value, valueErr := core.DecimalToUint256(candidate.Transition.Amount, contractCandidate.HomeLedger.Decimals) if valueErr != nil { return "", errors.Wrap(valueErr, "failed to convert native deposit amount to wei") } @@ -628,10 +601,18 @@ func (c *BlockchainClient) ChallengeEscrowDeposit(candidate core.State, challeng } func (c *BlockchainClient) FinalizeEscrowDeposit(candidate core.State) (string, error) { + if candidate.HomeChannelID == nil { + return "", errors.New("candidate state must have a home channel ID") + } if candidate.EscrowChannelID == nil { return "", errors.New("candidate state must have an escrow channel ID") } + channelIDBytes, err := hexToBytes32(*candidate.HomeChannelID) + if err != nil { + return "", errors.Wrap(err, "invalid channel ID") + } + escrowIDBytes, err := hexToBytes32(*candidate.EscrowChannelID) if err != nil { return "", errors.Wrap(err, "invalid escrow ID") @@ -650,7 +631,7 @@ func (c *BlockchainClient) FinalizeEscrowDeposit(candidate core.State) (string, return "", err } - tx, err := c.contract.FinalizeEscrowDeposit(c.transactOpts, escrowIDBytes, contractCandidate) + tx, err := c.contract.FinalizeEscrowDeposit(c.transactOpts, channelIDBytes, escrowIDBytes, contractCandidate) if err != nil { return "", errors.Wrap(err, "failed to finalize escrow deposit") } @@ -710,11 +691,19 @@ func (c *BlockchainClient) ChallengeEscrowWithdrawal(candidate core.State, chall } func (c *BlockchainClient) FinalizeEscrowWithdrawal(candidate core.State) (string, error) { + if candidate.HomeChannelID == nil { + return "", errors.New("candidate state must have a home channel ID") + } if candidate.EscrowChannelID == nil { return "", errors.New("candidate state must have an escrow channel ID") } if candidate.EscrowLedger == nil { - return "", errors.New("candidate state must have an escrow channel ID") + return "", errors.New("candidate state must have an escrow ledger") + } + + channelIDBytes, err := hexToBytes32(*candidate.HomeChannelID) + if err != nil { + return "", errors.Wrap(err, "invalid channel ID") } escrowIDBytes, err := hexToBytes32(*candidate.EscrowChannelID) @@ -727,15 +716,15 @@ func (c *BlockchainClient) FinalizeEscrowWithdrawal(candidate core.State) (strin return "", errors.Wrap(err, "failed to convert candidate state") } - if contractCandidate.Intent != core.INTENT_INITIATE_ESCROW_WITHDRAWAL { - return "", errors.New("unsupported intent for initiate escrow withdrawal: " + string(contractCandidate.Intent)) + if contractCandidate.Intent != core.INTENT_FINALIZE_ESCROW_WITHDRAWAL { + return "", errors.New("unsupported intent for finalize escrow withdrawal: " + string(contractCandidate.Intent)) } if err := c.checkFeeFn(context.Background(), c.transactOpts.From); err != nil { return "", err } - tx, err := c.contract.FinalizeEscrowWithdrawal(c.transactOpts, escrowIDBytes, contractCandidate) + tx, err := c.contract.FinalizeEscrowWithdrawal(c.transactOpts, channelIDBytes, escrowIDBytes, contractCandidate) if err != nil { return "", errors.Wrap(err, "failed to finalize escrow withdrawal") } @@ -746,18 +735,18 @@ func (c *BlockchainClient) FinalizeEscrowWithdrawal(candidate core.State) (strin func (c *BlockchainClient) EnsureSigValidatorRegistered(validatorID uint8, validatorAddress string, checkOnly bool) error { validatorAddr := common.HexToAddress(validatorAddress) - _validatorAddr, err := c.contract.GetNodeValidator(nil, c.nodeAddress, validatorID) + validatorInfo, err := c.contract.GetNodeValidator(nil, validatorID) if err != nil { return errors.Wrapf(err, "failed to check if validator %d is registered", validatorID) } - if _validatorAddr.Hex() == validatorAddr.Hex() { + if validatorInfo.Validator.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()) + } else if validatorInfo.Validator != (common.Address{}) { + return errors.Errorf("validator ID %d is already registered with a different address %s", validatorID, validatorInfo.Validator.Hex()) } if checkOnly { - return errors.Errorf("validator ID %d with address %s is not registered; run 'clearnode operator register-validator' to register", validatorID, validatorAddress) + return errors.Errorf("validator ID %d with address %s is not registered; run 'nitronode operator register-validator' to register", validatorID, validatorAddress) } if err := c.checkFeeFn(context.Background(), c.transactOpts.From); err != nil { @@ -768,11 +757,12 @@ func (c *BlockchainClient) EnsureSigValidatorRegistered(validatorID uint8, valid addressType, _ := abi.NewType("address", "", nil) uint256Type, _ := abi.NewType("uint256", "", nil) args := abi.Arguments{ + {Type: uint256Type}, + {Type: addressType}, {Type: uint8Type}, {Type: addressType}, - {Type: uint256Type}, } - message, err := args.Pack(validatorID, validatorAddr, new(big.Int).SetUint64(c.blockchainID)) + message, err := args.Pack(new(big.Int).SetUint64(c.blockchainID), c.channelHubContractAddress, validatorID, validatorAddr) if err != nil { return errors.Wrap(err, "failed to encode validator registration message") } @@ -782,7 +772,7 @@ func (c *BlockchainClient) EnsureSigValidatorRegistered(validatorID uint8, valid return errors.Wrap(err, "failed to sign validator registration message") } - _, err = c.contract.RegisterNodeValidator(c.transactOpts, c.nodeAddress, validatorID, validatorAddr, sig) + _, err = c.contract.RegisterNodeValidator(c.transactOpts, validatorID, validatorAddr, sig) if err != nil { return errors.Wrapf(err, "failed to register validator %d with address %s", validatorID, validatorAddress) } diff --git a/pkg/blockchain/evm/blockchain_test.go b/pkg/blockchain/evm/blockchain_test.go index 8d3318015..ab9db7d31 100644 --- a/pkg/blockchain/evm/blockchain_test.go +++ b/pkg/blockchain/evm/blockchain_test.go @@ -75,35 +75,6 @@ func TestNewBlockchainClient(t *testing.T) { mock.AssertExpectationsForObjects(t, mockEVMClient, mockAssetStore, mockSigner) } -func TestBlockchainClient_GetAccountsBalances(t *testing.T) { - t.Parallel() - mockEVMClient := new(MockEVMClient) - mockAssetStore := new(MockAssetStore) - mockSigner := new(MockSigner) - - setupMockSigner(t, mockSigner) - - contractAddress := common.HexToAddress("0xContract") - client, err := NewBlockchainClient(contractAddress, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) - require.NoError(t, err) - - accounts := []string{"0xUser1", "0xUser2"} - tokens := []string{"0xToken1"} - - // Mock successful return (uint256 = 100) - ret := common.LeftPadBytes(big.NewInt(100).Bytes(), 32) - mockEVMClient.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(ret, nil) - - balances, err := client.GetAccountsBalances(accounts, tokens) - require.NoError(t, err) - assert.Len(t, balances, 2) - assert.Len(t, balances[0], 1) - assert.Equal(t, "100", balances[0][0].String()) - assert.Equal(t, "100", balances[1][0].String()) - - mock.AssertExpectationsForObjects(t, mockEVMClient, mockAssetStore, mockSigner) -} - func TestBlockchainClient_GetNodeBalance(t *testing.T) { t.Parallel() mockEVMClient := new(MockEVMClient) diff --git a/pkg/blockchain/evm/channel_hub_abi.go b/pkg/blockchain/evm/channel_hub_abi.go index ff08ecff9..9c74aaf3c 100644 --- a/pkg/blockchain/evm/channel_hub_abi.go +++ b/pkg/blockchain/evm/channel_hub_abi.go @@ -62,8 +62,8 @@ type State struct { // ChannelHubMetaData contains all meta data concerning the ChannelHub contract. var ChannelHubMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_defaultSigValidator\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"DEFAULT_SIG_VALIDATOR\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ESCROW_DEPOSIT_UNLOCK_DELAY\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"MAX_DEPOSIT_ESCROW_PURGE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"MIN_CHALLENGE_DURATION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"TRANSFER_GAS_LIMIT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"VERSION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"challengeChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengerSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"challengerIdx\",\"type\":\"uint8\",\"internalType\":\"enumParticipantIndex\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"challengeEscrowDeposit\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"challengerSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"challengerIdx\",\"type\":\"uint8\",\"internalType\":\"enumParticipantIndex\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"challengeEscrowWithdrawal\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"challengerSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"challengerIdx\",\"type\":\"uint8\",\"internalType\":\"enumParticipantIndex\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"checkpointChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"claimFunds\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destination\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"closeChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"createChannel\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"initState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"depositToChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"depositToVault\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"escrowHead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"finalizeEscrowDeposit\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeEscrowWithdrawal\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeMigration\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getAccountBalance\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getChannelData\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumChannelStatus\"},{\"name\":\"definition\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"lastState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"lockedFunds\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getChannelIds\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEscrowDepositData\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumEscrowStatus\"},{\"name\":\"unlockAt\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"challengeExpiry\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"lockedAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"initState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEscrowDepositIds\",\"inputs\":[{\"name\":\"page\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"pageSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"ids\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEscrowWithdrawalData\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumEscrowStatus\"},{\"name\":\"challengeExpiry\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"lockedAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"initState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeValidator\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getOpenChannels\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"openChannels\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getReclaimBalance\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initiateEscrowDeposit\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"initiateEscrowWithdrawal\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initiateMigration\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"purgeEscrowDeposits\",\"inputs\":[{\"name\":\"maxToPurge\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerNodeValidator\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"validator\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawFromChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"withdrawFromVault\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"ChannelChallenged\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpireAt\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelCheckpointed\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelClosed\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"finalState\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelCreated\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"definition\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"initialState\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelDeposited\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelWithdrawn\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Deposited\",\"inputs\":[{\"name\":\"wallet\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositChallenged\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpireAt\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositFinalized\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositFinalizedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositInitiated\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositInitiatedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositsPurged\",\"inputs\":[{\"name\":\"purgedCount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalChallenged\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpireAt\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalFinalized\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalFinalizedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalInitiated\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalInitiatedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FundsClaimed\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"destination\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationInFinalized\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationInInitiated\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationOutFinalized\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationOutInitiated\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TransferFailed\",\"inputs\":[{\"name\":\"recipient\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"validatorId\",\"type\":\"uint8\",\"indexed\":true,\"internalType\":\"uint8\"},{\"name\":\"validator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractISignatureValidator\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Withdrawn\",\"inputs\":[{\"name\":\"wallet\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressCollision\",\"inputs\":[{\"name\":\"collision\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ChallengerVersionTooLow\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ECDSAInvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ECDSAInvalidSignatureLength\",\"inputs\":[{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ECDSAInvalidSignatureS\",\"inputs\":[{\"name\":\"s\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"EmptySignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectAmount\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectChallengeDuration\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectChannelStatus\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectStateIntent\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectValue\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientBalance\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidValidatorId\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NativeTransferFailed\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"OnlyNonHomeEscrowsCanBeChallenged\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SafeCastOverflowedIntToUint\",\"inputs\":[{\"name\":\"value\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"type\":\"error\",\"name\":\"SafeERC20FailedOperation\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotApproved\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"}]}]", - Bin: "0x60a0346100aa57601f61604f38819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615f8c90816100c3823960805181818161114f0152614ac40152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806313c380ed1461024457806316b390b11461023f57806317536c061461023a578063187576d8146102355780633115f6301461023057806338a66be21461022b57806341b660ef1461022657806347de477a14610221578063532691981461021c578063587675e8146102175780635a0745b4146102125780635b9acbf91461020d5780635dc46a74146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f45780637e7985f9146101ef57806382d3e15d146101ea5780638d0b12a5146101e557806394191051146101e05780639691b468146101db578063a5c82680146101d6578063b00b6fd6146101d1578063b25a1d38146101cc578063beed9d5f146101c7578063c74a2d10146101c2578063d888ccae146101bd578063dc23f29e146101b8578063dd73d494146101b3578063e617208c146101ae578063ecf3d7e8146101a9578063f4ac51f5146101a4578063f766f8d61461019f5763ffa1ad741461019a575f80fd5b612680565b6124e1565b612466565b612354565b6122b5565b612137565b611f3b565b611dfb565b611bb7565b611a11565b611701565b611698565b6114fd565b6113a3565b611386565b6111ef565b6111d2565b6111bb565b61117e565b61113a565b61111f565b611033565b611021565b610fff565b610fe3565b610f9d565b610d3b565b610b82565b6108a6565b610852565b6106c6565b610640565b610532565b610326565b61028f565b90816102609103126102585790565b5f80fd5b90604060031983011261025857600435916024359067ffffffffffffffff82116102585761028c91600401610249565b90565b34610258576102a66102a03661025c565b90612c38565b005b9181601f840112156102585782359167ffffffffffffffff8311610258576020838186019501011161025857565b60643590600282101561025857565b90606060031983011261025857600435916024359067ffffffffffffffff821161025857610315916004016102a8565b909160443560028110156102585790565b3461025857610334366102e5565b61040061034c859493945f52600260205260405f2090565b92835461036761036261035e83613f07565b1590565b612e9a565b6103c2600286019461039261038387546001600160a01b031690565b948560038a019a8b5492614aab565b9591600160068b019a01966103b288546001600160a01b039060081c1690565b926103bc8c61300e565b88614bfb565b60c06103cd886142d5565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301613092565b038173__$682d6198b4eca5bc7e038b912a26498e7e$__5af49081156104f0577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e8019661049e956080955f946104b3575b508361047c61046e61048f9697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104888a61300e565b908c6146aa565b015167ffffffffffffffff1690565b906104ae604051928392836130a3565b0390a2005b61048f94509461047c6104e061046e9760c03d60c0116104e9575b6104d8818361292e565b8101906129a5565b95505094610450565b503d6104ce565b612a9f565b6001600160a01b0381160361025857565b60031960609101126102585760043561051e816104f5565b9060243561052b816104f5565b9060443590565b6001600160a01b0361054336610506565b9290911690610553821515613200565b61055e83151561322f565b815f5260066020526105848160405f20906001600160a01b03165f5260205260405f2090565b805491848301809311610602577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b0392556105c5615951565b6105d0858233614d31565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b61325e565b60206040818301928281528451809452019201905f5b81811061062a5750505090565b825184526020938401939092019160010161061d565b34610258576020600319360112610258576001600160a01b03600435610665816104f5565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b8181106106b0576106ac856106a08187038261292e565b60405191829182610607565b0390f35b8254845260209093019260019283019201610689565b3461025857602060031936011261025857600354600480549190355f5b82841080610849575b1561083c5761071861070a61070086613496565b90549060031b1c90565b5f52600260205260405f2090565b600181016003610729825460ff1690565b61073281611c46565b1461082a5761074082615bbd565b156107e657915f826107d16107dd9560056107d79601926107c26107ba8554926107a5600d61079361077c60028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254613272565b9055600360ff19825416179055565b55613982565b93613982565b915b91926106e3565b50509290506107f59150600455565b806107fc57005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b50509261083690613982565b916107df565b9290506107f59150600455565b508181106106ec565b34610258575f600319360112610258576020604051620186a08152f35b90600319820160e081126102585760c0136102585760049160c4359067ffffffffffffffff82116102585761028c91600401610249565b6108af3661086f565b90602082019160026108c08461287e565b6108c981611c55565b148015610b67575b8015610b49575b6108e190612888565b6109896108f66108f1368561328d565b614f1d565b9161090084614fdb565b602084019061090e826128b7565b9561092d6040870197610920896128b7565b608089013591858961428c565b60e08261095561094e61094261077c8c6128b7565b6107a5608085016128b7565b54886150e6565b60405196879283927fa8b4483c000000000000000000000000000000000000000000000000000000008452600484016133d1565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49384156104f057610a846001600160a01b0394610a9c93610a057fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a8a955f91610b1a575b506109f4368d61328d565b6109fe368a612b8a565b908c61527e565b610a3189610a2c610a15856128b7565b6001600160a01b03165f52600160205260405f2090565b615bf1565b506002610a3d8261287e565b610a4681611c55565b03610aa15750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a7c898261286d565b0390a26128b7565b976128b7565b918360405194859416981696836133e8565b0390a4005b610aac60039161287e565b610ab581611c55565b03610aea57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a7c898261286d565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a7c898261286d565b610b3c915060e03d60e011610b42575b610b34818361292e565b810190613301565b5f6109e9565b503d610b2a565b506108e1610b568461287e565b610b5f81611c55565b1590506108d8565b506003610b738461287e565b610b7c81611c55565b146108d1565b610b8b3661086f565b90610bac6004610b9d6020850161287e565b610ba681611c55565b14612888565b610bb96108f1368361328d565b9160208201610bc7816128b7565b90610be860408501926080610bdb856128b7565b960135958691868961428c565b610bfa610bf484613478565b86615391565b93610c0486613f07565b15610c4c57505050610c4781610c3b7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98093866153c1565b6040519182918261286d565b0390a3005b610c949060c085610c6088979596976142d5565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401612a7a565b038173__$682d6198b4eca5bc7e038b912a26498e7e$__5af480156104f0577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610c4795610d09945f93610d12575b50610cf1610cf7916128b7565b916128b7565b91610d023687612b8a565b8a8a6146aa565b610c3b846134e5565b610cf7919350610d33610cf19160c03d60c0116104e9576104d8818361292e565b939150610ce4565b3461025857610d493661025c565b610d5a6009610b9d6020840161287e565b610d766001610d70845f525f60205260405f2090565b01613533565b610e55610d8d60208301516001600160a01b031690565b9161077c60e060408301610db9610dab82516001600160a01b031690565b608086015190888a8c61428c565b610e21610e1a610e03610dcc368b612b8a565b9586946101408c018d8d610ddf83613478565b67ffffffffffffffff1646149d8e610f0f575b50505050516001600160a01b031690565b6060840151602001516001600160a01b03166107a5565b54896150e6565b60405195869283927fa8b4483c000000000000000000000000000000000000000000000000000000008452600484016135bf565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49182156104f057610e87935f93610eee575b508661527e565b15610ebd576104ae7f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c3916040519182918261286d565b6104ae7f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c826916040519182918261286d565b610f0891935060e03d60e011610b4257610b34818361292e565b915f610e80565b610f33610f6992610f24610f6e963690612aaa565b60608d01526060369101612aaa565b60808b0152610f406135ab565b60a08b0152610f4d6135ab565b60c08b01526001600160a01b03165f52600160205260405f2090565b615c9a565b505f8d8d82610df2565b600319604091011261025857600435610f90816104f5565b9060243561028c816104f5565b34610258576020610fda6001600160a01b03610fb836610f78565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610258575f600319360112610258576020604051612a308152f35b34610258576040600319360112610258576106ac6106a0602435600435613669565b6102a661102d3661025c565b90613723565b34610258576020600319360112610258576001600160a01b03600435611058816104f5565b165f52600160205261106c60405f20615b75565b5f905f5b815181101561110c5761109e6110976110898385613655565b515f525f60205260405f2090565b5460ff1690565b6110a781612208565b600381141590816110f7575b506110c1575b600101611070565b916110d48184600193106110dc57613982565b9290506110b9565b6110e68585613655565b516110f18286613655565b52613982565b6005915061110481612208565b14155f6110b3565b506106ac91815260405191829182610607565b34610258575f60031936011261025857602060405160408152f35b34610258575f600319360112610258576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610258576020610fda6001600160a01b0361119936610f78565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610258576102a66111cc3661025c565b90613a5e565b34610258575f600319360112610258576020600454604051908152f35b346102585761127a611200366102e5565b92939190611216855f52600560205260405f2090565b9161122761036261035e8554613f07565b6002830160a061124761124183546001600160a01b031690565b896153dc565b604051809781927f24063eba00000000000000000000000000000000000000000000000000000000835260048301613c4b565b038173__$b69fb814c294bfc16f92e50d7aeced4bde$__5af49485156104f0575f95611355575b508354600185015490949060081c6001600160a01b0316968792546112cc906001600160a01b031690565b809581956003850154976112e1928992614aab565b9a9190946006019a6112f28c61300e565b956112fd968b614bfb565b846113078761300e565b6113119589615444565b6060015167ffffffffffffffff1660405191829161132f91836130a3565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61137891955060a03d60a01161137f575b611370818361292e565b810190613990565b935f6112a1565b503d611366565b34610258575f600319360112610258576020604051620151808152f35b6114796113af3661025c565b6113d06113c16020839594950161287e565b6113ca81611c55565b15612888565b6113e66001610d70855f525f60205260405f2090565b9060e08161144561143e61094261077c61140a60208901516001600160a01b031690565b6114318b8a604081019389608061142887516001600160a01b031690565b9301519361428c565b516001600160a01b031690565b54876150e6565b60405195869283927fa8b4483c000000000000000000000000000000000000000000000000000000008452600484016133d1565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104f0577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc936104ae93610c3b925f926114dc575b506114d53685612b8a565b908761527e565b6114f691925060e03d60e011610b4257610b34818361292e565b905f6114ca565b346102585761150b3661086f565b9061151d6006610b9d6020850161287e565b61152a6108f1368361328d565b9160208201611538816128b7565b9061154c60408501926080610bdb856128b7565b611558610bf484613478565b9361156286613f07565b1561159957505050610c4781610c3b7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c93866153c1565b6115e29060a0856115af611241879896976128b7565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613a47565b038173__$b69fb814c294bfc16f92e50d7aeced4bde$__5af480156104f0577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610c4795610c3b945f93611651575b50610cf161163f916128b7565b9161164a3687612b8a565b8a8a615444565b61163f919350611672610cf19160a03d60a01161137f57611370818361292e565b939150611632565b6024359060ff8216820361025857565b359060ff8216820361025857565b34610258576040600319360112610258576001600160a01b036116e66004356116c0816104f5565b826116c961167a565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102585760043560243567ffffffffffffffff811161025857611730903690600401610249565b60443567ffffffffffffffff8111610258576117509036906004016102a8565b919061175a6102d6565b9261176c855f525f60205260405f2090565b9161177960018401613533565b916117a3611788855460ff1690565b61179181612208565b600181149081156119fd575b50613c5c565b866117b06005860161300e565b926117f16117bd88613478565b67ffffffffffffffff6117e86117db885167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613c8b565b60208501516001600160a01b0316926040860161181581516001600160a01b031690565b9567ffffffffffffffff61183d6117db61182e8d613478565b935167ffffffffffffffff1690565b911611611903575b50946118a77f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99866118d5966118a16118c5978d61189560149f9e9c996118bc9a6118f49f60808c015192614aab565b93919490923690612b8a565b90614bfb565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613cba565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b6104ae60405192839283613cdc565b8861144561193f61094261077c60e0956114318c9f9e9c9a6119469b8d9f9a9b9d6119336113c160208c0161287e565b8960808d01519361428c565b548d6150e6565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49384156104f0577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118f4986118a18e8c61189560149f976118a7986118bc9b6119c66118d59f6118c59f5f916119de575b508d6119bf3689612b8a565b9089615581565b9a9f5050995050509750509650969899509950611845565b6119f7915060e03d60e011610b4257610b34818361292e565b5f6119b3565b60049150611a0a81612208565b145f61179d565b3461025857608060031936011261025857600435611a2e816104f5565b611a3661167a565b90604435611a43816104f5565b60643567ffffffffffffffff811161025857611b906001600160a01b0392611b5d611a9e96611b42611b3d611a7d899736906004016102a8565b60ff85169a91611b3790611a928d1515613d04565b8b89169d8e1515613200565b611afa8785611af4611ae8611ae8611adb85611acb866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613d33565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611b3060808561292e565b3691612b39565b9061578a565b613d78565b611acb856001600160a01b03165f52600760205260405f2090565b906001600160a01b03167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611bd7611bc33661025c565b6113d06003610b9d6020849695960161287e565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104f0577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf986936104ae93610c3b925f926114dc57506114d53685612b8a565b634e487b7160e01b5f52602160045260245ffd5b60041115611c5057565b611c32565b600a1115611c5057565b90600a821015611c505752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61028c9167ffffffffffffffff8251168152611cb560208301516020830190611c5f565b60408201516040820152611d236060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611da560a0840151610260610220850152610260840190611c6c565b92015190610240818403910152611c6c565b929367ffffffffffffffff60c09561028c98979482948752611dd881611c46565b602087015216604085015216606083015260808201528160a08201520190611c91565b3461025857602060031936011261025857600435611e17613ddd565b505f52600260205260405f20611e2b612951565b90805482526106ac600182015491611e77611e67611e498560ff1690565b94611e58602088019687613e21565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e9e611e8e60028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611ed6905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611f2a61182e611f06600660058501549460e087019586520161300e565b93610100810194855251965197611f1c89611c46565b5167ffffffffffffffff1690565b905191519260405196879687611db7565b3461025857611f493661086f565b611f5a6008610b9d6020840161287e565b80611fcf611f6b6108f1368661328d565b936020810160e0611f7b826128b7565b91611f9a6040850193611f8d856128b7565b608087013591898c61428c565b610e21610e1a610e0361077c611fb0368b612b8a565b9687958d8a611fbe82613f07565b9d8e15612098575b505050506128b7565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49182156104f05761200c935f93612073575b5061200690369061328d565b8661527e565b15612042576104ae7f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a916040519182918261286d565b6104ae7f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b8916040519182918261286d565b6120069193506120919060e03d60e011610b4257610b34818361292e565b9290611ffa565b6120f1936120cd610a15926120af610a2c95614fdb565b8c60606120c0366101408501612aaa565b9101526060369101612aaa565b60808c01526120da6135ab565b60a08c01526120e76135ab565b60c08c01526128b7565b505f8d8a8e611fc6565b9160a09367ffffffffffffffff9161028c979693855261211a81611c46565b602085015216604083015260608201528160808201520190611c91565b3461025857602060031936011261025857600435612153613ddd565b505f52600560205260405f20612167612961565b90805482526106ac600182015491612185611e67611e498560ff1690565b61219c611e8e60028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121f76121e2600660058501549460c085019586520161300e565b9160e0810192835251945195611f1c87611c46565b9151905191604051958695866120fb565b60061115611c5057565b906006821015611c505752565b919261229761012094612239856122aa959a99989a612212565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c91565b946101008201520152565b34610258576020600319360112610258576004355f60a06040516122d8816128d5565b82815282602082015282604082015282606082015282608082015201526122fd613ddd565b505f525f60205261231060405f20613e39565b805161231b81612208565b6106ac60208301519260408101519060606123446117db608084015167ffffffffffffffff1690565b910151916040519586958661221f565b346102585761236236610506565b90916123786001600160a01b0382161515613200565b61238382151561322f565b335f5260066020526123a98360405f20906001600160a01b03165f5260205260405f2090565b549082821061243e57828203918211610602578383916123fe936123f8836123e2336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55613f64565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610c473394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b6124866124723661025c565b6113d06002610b9d6020849695960161287e565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104f0577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f41778696206696936104ae93610c3b925f926114dc57506114d53685612b8a565b34610258576124ef36610f78565b6124f7615951565b6001600160a01b0381169161250d831515613200565b6001600160a01b03612534826123e2336001600160a01b03165f52600860205260405f2090565b549161254183151561322f565b5f612561826123e2336001600160a01b03165f52600860205260405f2090565b55169181836125db57612584915f808080858a5af161257e613e97565b50613ec6565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a46102a660017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f5114811615612661575b604091909152612584577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612677573d15843b15151616612622565b503d5f823e3d90fd5b34610258575f60031936011261025857602060405160018152f35b906126a581611c46565b60ff60ff198354169116179055565b67ffffffffffffffff81160361025857565b35906126d1826126b4565b565b600a111561025857565b35906126d1826126d3565b60c0809167ffffffffffffffff8135612700816126b4565b1684526001600160a01b036020820135612719816104f5565b16602085015260ff61272d6040830161168a565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561025857016020813591019167ffffffffffffffff821161025857813603831361025857565b601f8260209493601f1993818652868601375f8582860101520116010190565b61028c9167ffffffffffffffff82356127e0816126b4565b1681526127fe60208301356127f4816126d3565b6020830190611c5f565b6040820135604082015261281860608201606084016126e8565b61282a610140820161014084016126e8565b61285e61285261283e610220850185612758565b6102606102208601526102608501916127a8565b92610240810190612758565b916102408185039101526127a8565b90602061028c9281815201906127c8565b3561028c816126d3565b1561288f57565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b3561028c816104f5565b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176128f157604052565b6128c1565b60e0810190811067ffffffffffffffff8211176128f157604052565b60a0810190811067ffffffffffffffff8211176128f157604052565b90601f601f19910116810190811067ffffffffffffffff8211176128f157604052565b604051906126d16101208361292e565b604051906126d16101008361292e565b604051906126d160e08361292e565b5190600482101561025857565b51906126d1826126b4565b5190811515820361025857565b908160c091031261025857612a0d60a0604051926129c2846128d5565b80518452602081015160208501526129dc60408201612980565b604085015260608101516129ef816126b4565b60608501526080810151612a02816126b4565b608085015201612998565b60a082015290565b908151612a2181611c46565b815260a080612a3f602085015160c0602086015260c0850190611c91565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b9091612a9161028c93604084526040840190612a15565b9160208184039101526127c8565b6040513d5f823e3d90fd5b91908260e091031261025857604051612ac2816128f6565b60c08082948035612ad2816126b4565b84526020810135612ae2816104f5565b6020850152612af36040820161168a565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116128f157601f01601f191660200190565b929192612b4582612b1d565b91612b53604051938461292e565b829481845281830111610258578281602093845f960137010152565b9080601f830112156102585781602061028c93359101612b39565b9190916102608184031261025857612ba0612971565b92612baa826126c6565b8452612bb8602083016126dd565b602085015260408201356040850152612bd48160608401612aaa565b6060850152612be7816101408401612aaa565b608085015261022082013567ffffffffffffffff81116102585781612c0d918401612b6f565b60a085015261024082013567ffffffffffffffff811161025857612c319201612b6f565b60c0830152565b612c4a815f52600260205260405f2090565b60018101805490600882901c6001600160a01b031690612c7460028501546001600160a01b031690565b92612c89612c828654613f07565b9160ff1690565b90159081612e86575b5080612e63575b612ddd5750612caf6005610b9d6020880161287e565b8254612cba81613f07565b15612d05575091612cf2612d00927f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c948784546149af565b54936040519182918261286d565b0390a3565b612d5690612d1e6003869496019185878a85549361428c565b60c087610c60612d4f612d42886001600160a01b03165f52600660205260405f2090565b6107a561016085016128b7565b548961437a565b038173__$682d6198b4eca5bc7e038b912a26498e7e$__5af49384156104f0577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e95612d0095612cf2945f91612dbe575b508554935493612db7368c612b8a565b908a6146aa565b612dd7915060c03d60c0116104e9576104d8818361292e565b5f612da7565b805460ff19166003179055507f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e91612d0091612cf29060058301905f82549255612e4c600485017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b600d84015460401c6001600160a01b031690613f64565b506004840154612e7f9060401c67ffffffffffffffff166117db565b4211612c99565b60029150612e9381611c46565b145f612c92565b15612ea157565b7ff64df366000000000000000000000000000000000000000000000000000000005f5260045ffd5b90604051612ed6816128f6565b60c060048294612f1460ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c92168015612f67575b6020831014612f5357565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612f48565b5f9291815491612f8083612f39565b8083529260018116908115612fd55750600114612f9c57505050565b5f9081526020812093945091925b838310612fbb575060209250010190565b600181602092949394548385870101520191019190612faa565b9050602094955060ff1991509291921683830152151560051b010190565b906126d16130079260405193848092612f71565b038361292e565b9060405161301b816128f6565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c5057600d61308d9160c09360208601526001810154604086015261306060028201612ec9565b606086015261307160078201612ec9565b6080860152613082600c8201612ff3565b60a086015201612ff3565b910152565b90602061028c928181520190612a15565b929160206131ef6126d193604087526130d7815467ffffffffffffffff811660408a015260ff60608a019160401c16611c5f565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d6131bd6102a08901600c8401612f71565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612f71565b94019067ffffffffffffffff169052565b1561320757565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b1561323657565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161060257565b63ffffffff81160361025857565b91908260c0910312610258576040516132a5816128d5565b60a080829480356132b58161327f565b845260208101356132c5816104f5565b602085015260408101356132d8816104f5565b604085015260608101356132eb816126b4565b6060850152608081013560808501520135910152565b908160e09103126102585760405190613319826128f6565b8051825260208101516020830152604081015160068110156102585761337a9160c091604085015261334d6060820161298d565b606085015261335e60808201612998565b608085015261336f60a08201612998565b60a085015201612998565b60c082015290565b9061338e818351612212565b608067ffffffffffffffff816133b3602086015160a0602087015260a0860190611c91565b94604081015160408601526060810151606086015201511691015290565b9091612a9161028c93604084526040840190613382565b60e09060a061028c949363ffffffff81356134028161327f565b1683526001600160a01b03602082013561341b816104f5565b1660208401526001600160a01b036040820135613437816104f5565b16604084015267ffffffffffffffff6060820135613454816126b4565b16606084015260808101356080840152013560a08201528160c082015201906127c8565b3561028c816126b4565b634e487b7160e01b5f52603260045260245ffd5b6003548110156134ae5760035f5260205f2001905f90565b613482565b80548210156134ae575f5260205f2001905f90565b916134e1918354905f199060031b92831b921b19161790565b9055565b600354680100000000000000008110156128f157600181016003556003548110156134ae5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b90604051613540816128d5565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261359a67ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906135ba60208361292e565b5f8252565b90916135d661028c93604084526040840190613382565b916020818403910152611c91565b67ffffffffffffffff81116128f15760051b60200190565b6040519061360b60208361292e565b5f808352366020840137565b90613621826135e4565b61362e604051918261292e565b828152601f1961363e82946135e4565b0190602036910137565b9190820391821161060257565b80518210156134ae5760209160051b010190565b9190600354908084029380850482149015171561060257818410156136ed57830190818411610602578082116136e5575b506136ad6136a88483613648565b613617565b92805b8281106136bc57505050565b806136cb610700600193613496565b6136de6136d88584613648565b88613655565b52016136b0565b90505f61369a565b5050905061028c6135fc565b906006811015611c505760ff60ff198354169116179055565b90602061028c928181520190611c91565b906137356001610b9d6020840161287e565b613746825f525f60205260405f2090565b61375260018201613533565b9161375e825460ff1690565b918461376c6005830161300e565b91604086019261378384516001600160a01b031690565b91600261379a60208a01516001600160a01b031690565b976137a481612208565b1480613962575b6138a75750506137d661094e61094261077c61380a9661143160e0978a978c898f608001519361428c565b60405193849283927fa8b4483c000000000000000000000000000000000000000000000000000000008452600484016133d1565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af480156104f057610f6961388194610a1588937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613874965f92613886575b5061386d3689612b8a565b908661527e565b506040519182918261286d565b0390a2565b6138a091925060e03d60e011610b4257610b34818361292e565b905f613862565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613881969195506139559450610f699261390c6014836138f4610a1595600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016139358151606061392b60208301516001600160a01b031690565b9101519086613f64565b5160a061394c60208301516001600160a01b031690565b91015191613f64565b5060405191829182613712565b5061397b6117db601483015467ffffffffffffffff1690565b42116137ab565b5f1981146106025760010190565b908160a0910312610258576139e56080604051926139ad84612912565b80518452602081015160208501526139c760408201612980565b604085015260608101516139da816126b4565b606085015201612998565b608082015290565b9081516139f981611c46565b815260806001600160a01b0381613a1f602086015160a0602087015260a0860190611c91565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612a9161028c936040845260408401906139ed565b613a70815f52600560205260405f2090565b8054600182018054919492600883901c6001600160a01b031691613a9e60028301546001600160a01b031690565b93613aab612c8289613f07565b90159081613c37575b5080613c17575b613bb65750613ad16007610b9d6020870161287e565b613ada86613f07565b15613b11575091610c3b612d00927f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989483886149af565b6003613b37919492940192613b2a84548287868b61428c565b60a0836115af83896153dc565b038173__$b69fb814c294bfc16f92e50d7aeced4bde$__5af49081156104f0577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d195612d0095610c3b945f94613b95575b50549261164a3687612b8a565b613baf91945060a03d60a01161137f57611370818361292e565b925f613b88565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1949650612cf291925092613bf5612d0094600360ff19825416179055565b60058301905f82549255612e4c6004850167ffffffffffffffff198154169055565b50613c306117db600484015467ffffffffffffffff1690565b4211613abb565b60029150613c4481611c46565b145f613ab4565b90602061028c9281815201906139ed565b15613c6357565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613c9257565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161060257565b9067ffffffffffffffff613cfd6020929594956040855260408501906127c8565b9416910152565b15613d0b57565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613d3c575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613d7f57565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613db4826128f6565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613dea826128f6565b606060c0835f81525f60208201525f6040820152613e06613da7565b83820152613e12613da7565b60808201528260a08201520152565b613e2a82611c46565b52565b6006821015611c505752565b90604051613e4681612912565b608067ffffffffffffffff60148395613e6360ff82541686613e2d565b613e6f60018201613533565b6020860152613e806005820161300e565b604086015260138101546060860152015416910152565b3d15613ec1573d90613ea882612b1d565b91613eb6604051938461292e565b82523d5f602084013e565b606090565b15613ecf575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b805f525f60205260ff60405f2054166006811015611c50578015908115613f50575b50613f4b575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150613f5d81612208565b145f613f29565b90613f779291613f72615951565b613fac565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b90816020910312610258575190565b9190918115614236576001600160a01b0383169283614050576001600160a01b038216925f8080808488620186a0f1613fe3613e97565b5015613ff0575050505050565b614033612d00926123e27fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b61403e828254613272565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa9081156104f0575f91614217575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f918291906140e860648261292e565b51908286620186a0f1906140fa613e97565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104f05786915f936141e6575b50836141db575b836141c7575b5050501561415c575b50505050565b816141a56001600160a01b03926123e27fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b6141b0858254613272565b90556040519384521691602090a35f808080614156565b6141d2929350613648565b145f848161414d565b818110159350614147565b61420991935060203d602011614210575b614201818361292e565b810190613f9d565b915f614140565b503d6141f7565b614230915060203d60201161421057614201818361292e565b5f614093565b505050565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610258570180359067ffffffffffffffff82116102585760200191813603831361025857565b92916126d1946142bd6142cc926142b283876142ac61022089018961423b565b90614aab565b90878a9493946159c8565b836142ac61024085018561423b565b929091946159c8565b5f604051916142e3836128d5565b818352614376602084016142f5613ddd565b81526143686040860191858352611ec66004606089019288845260808a019589875261433060a08c01998b8b525f52600260205260405f2090565b9160ff60018401541661434281611c46565b8c526143506006840161300e565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614388836128d5565b5f83526143766020840161439a613ddd565b815261436860408601915f8352611ec6600460608901925f845260808a01955f875261433060a08c01995f8b525f52600260205260405f2090565b9060c060049161440867ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116144b857505050565b5f5260205f20906020601f840160051c830193106144f0575b601f0160051c01905b8181106144e5575050565b5f81556001016144da565b90915081906144d1565b919091825167ffffffffffffffff81116128f1576145228161451c8454612f39565b846144ab565b6020601f821160011461455c5781906134e19394955f92614551575b50505f198260011b9260031b1c19161790565b015190505f8061453e565b601f1982169061456f845f5260205f2090565b915f5b8181106145a957509583600195969710614591575b505050811b019055565b01515f1960f88460031b161c191690555f8080614587565b9192602060018192868b015181550194019201614572565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c5057600a831015611c505760c0600d916126d1947fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000086549260401b169116178455604081015160018501556146536060820151600286016143d5565b6146646080820151600786016143d5565b61467560a0820151600c86016144fa565b015191016144fa565b7f80000000000000000000000000000000000000000000000000000000000000008114610602575f0390565b60209392916146c761472e929897985f52600260205260405f2090565b966040850180516146d781611c46565b6146e081611c46565b614992575b5060a08501916146f58351151590565b6148fb575b5050606084015167ffffffffffffffff16806148d0575b50608084015167ffffffffffffffff1680614881575b5051151590565b1561486857608001518201516001600160a01b031680935b8251905f8213156148285761476891506147608451615a25565b928391614cef565b61477760058601918254613272565b90555b0180515f8113156147d65750916147b56005926123e261479d6147cb9651615a25565b966001600160a01b03165f52600660205260405f2090565b6147c0858254613648565b905501918254613272565b90555b6126d1614e1b565b9290505f83126147ea575b505050506147ce565b6148076005926123e261479d61480261481d9761467e565b615a25565b614812858254613272565b905501918254613648565b90555f8080806147e1565b5f8212614838575b50505061477a565b61484761480261484f9361467e565b928391613f64565b61485e60058601918254613648565b9055825f80614830565b50600d84015460401c6001600160a01b03168093614746565b6148ca9060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614727565b6148f590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f614711565b6149088560068b016145c1565b88556001880180547fffffffffffffffffffffff0000000000000000000000000000000000000000ff16600889901b74ffffffffffffffffffffffffffffffffffffffff00161790556002880180547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b038b1617905560038801555f806146fa565b6149a990516149a081611c46565b60018a0161269b565b5f6146e5565b9091926149dd614a04946149cf6001610d70865f525f60205260405f2090565b92608084015191868661428c565b60e0836109556149fd61094261077c60408701516001600160a01b031690565b54856150e6565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104f0576126d1945f94614a41575b50614a3b903690612b8a565b9161527e565b614a3b919450614a5f9060e03d60e011610b4257610b34818361292e565b9390614a2f565b15614a6f575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215614b7e57843560f81c9081614af857507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150614af49050565b9091565b600180915f97939594975060ff86161c1603614b5657614b4983614b37611adb614af496611acb8a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515614a66565b600101915f199190910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c5057565b9392606093614bed6001600160a01b0394613cfd949998998852608060208901526080880190611c6c565b9186830360408801526127a8565b906001600160a01b039295602097614c2f9599614c66614c1d614cab95615a5a565b614c58604051998a928e840190614ba6565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f19810189528861292e565b614c6f81614bb8565b614ce857505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614bc2565b0392165afa80156104f0576126d1915f91614cc9575b501515613d78565b614ce2915060203d60201161421057614201818361292e565b5f614cc1565b9050614c75565b90613f779291614cfd615951565b614d31565b15614d0957565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614236576001600160a01b03169182158015614e0c57614d55823414614d02565b15614d5f57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614df6575b6040919091525f60605215614dc25750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612677573d15833b15151616614db0565b614e163415614d02565b614d55565b6003546004545f5b82821080614f13575b15614f0857614e4061070a61070084613496565b600181016003614e51825460ff1690565b614e5a81611c46565b14614ef657614e6882615bbd565b15614eb357915f826107d1614eaa956005614ea49601926107c26107ba8554926107a5600d61079361077c60028501546001600160a01b031690565b91613982565b915b9190614e23565b50509150614ec090600455565b80614ec85750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614f0290613982565b91614eac565b9150614ec090600455565b5060408110614e2c565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f010000000000000000000000000000000000000000000000000000000000000091604051614fc460208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c08152614fd360e08261292e565b519020161790565b6020810135614fe9816104f5565b6001600160a01b03811690614fff821515613200565b60408301359061500e826104f5565b6150356001600160a01b03831692615027841515613200565b615030836104f5565b6104f5565b61503e816104f5565b50811461508a575063ffffffff62015180913561505a8161327f565b161061506257565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b604051906150c282612912565b5f6080838281526150d1613ddd565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916150fb6150b5565b935f525f60205260405f209061511560ff83541686613e2d565b6151216005830161300e565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b16911617845561526d6001850161521b6151e760408501516001600160a01b031690565b82906001600160a01b03167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b926152ba816152f19460c09461529b885f525f60205260405f2090565b976152a7895460ff1690565b6152b081612208565b1561537f57615581565b6040810180516152c981612208565b6152d281612208565b151580615354575b61533a575b5060a0810151615312575b0151151590565b6152f85750565b5f6013820155601401805467ffffffffffffffff19169055565b835460ff191660011784556153356014850167ffffffffffffffff198154169055565b6152ea565b61534e905161534881612208565b856136f9565b5f6152df565b50845460ff1681519061536682612208565b61536f82612208565b61537881612208565b14156152da565b61538c8260018b01615140565b615581565b9067ffffffffffffffff6040519160208301938452166040820152604081526153bb60608261292e565b51902090565b90614a04916149dd6001610d70835f525f60205260405f2090565b906001600160a01b03906153ee6150b5565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661541681611c46565b86526154246006820161300e565b602087015260058101546040870152015416606084015216608082015290565b602093929161546161472e929897985f52600560205260405f2090565b9660408501805161547181611c46565b61547a81611c46565b61556d575b50608085019161548f8351151590565b6154d6575b5050606084015167ffffffffffffffff16806154b1575051151590565b6148ca90600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b6154e38560068b016145c1565b88556001880180547fffffffffffffffffffffff0000000000000000000000000000000000000000ff16600889901b74ffffffffffffffffffffffffffffffffffffffff00161790556002880180547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b038b1617905560038801555f80615494565b61557b90516149a081611c46565b5f61547f565b6155976060919493945f525f60205260405f2090565b936155a56080850151151590565b615778575b01916155fd60c06155c66020865101516001600160a01b031690565b9280515f811361573a575b506020810180515f81136156f2575b5081515f81126156b1575b50515f8112615666575b500151151590565b80615658575b615614575b505050506126d1614e1b565b61564d9261564060a092615634604060139601516001600160a01b031690565b90848451015191613f64565b5101519201918254613648565b90555f808080615608565b5060a0835101511515615603565b6148026156729161467e565b61568d856123e261077c60408a01516001600160a01b031690565b615698828254613272565b90556156a960138901918254613648565b90555f6155f5565b6148026156bd9161467e565b6156db81876156d660208b01516001600160a01b031690565b613f64565b6156ea60138a01918254613648565b90555f6155eb565b6156fb90615a25565b615716866123e261077c60408b01516001600160a01b031690565b615721828254613648565b905561573260138a01918254613272565b90555f6155e0565b61574390615a25565b615761818661575c60208a01516001600160a01b031690565b614cef565b61577060138901918254613272565b90555f6155d1565b61578581600587016145c1565b6155aa565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615929575b806d04ee2d6d415b85acef8100000000600a92101561590d575b662386f26fc100008110156158f8575b6305f5e1008110156158e6575b6127108110156158d6575b60648110156158c7575b10156158bc575b6158505f19602161581a60018901615dac565b978801015b01917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561586357615850905f199061581f565b50506001600160a01b036158888461587c858498615d40565b60208151910120615d96565b9116931683146158b4576158a691816020611ae89351910120615d96565b146158af575f90565b600190565b505050600190565b600190940193615807565b60029060649004960195615800565b60049061271090049601956157f6565b6008906305f5e10090049601956157eb565b601090662386f26fc1000090049601956157de565b6020906d04ee2d6d415b85acef810000000090049601956157ce565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046157b4565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146159a05760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b6001600160a01b0390614cab6159ee6159e960209895999697993690612b8a565b615a5a565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614bc2565b5f8112615a2f5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b67ffffffffffffffff815116906020810151600a811015611c5057615b03826040615b64940151615aa260806060840151930151946040519760208901526040880190611c5f565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261028c6102408261292e565b90604051918281549182825260208201905f5260205f20925f5b818110615ba45750506126d19250038361292e565b8454835260019485019487945060209093019201615b8f565b67ffffffffffffffff6004820154164210159081615bd9575090565b600180925060ff91015416615bed81611c46565b1490565b6001810190825f528160205260405f2054155f14615c59578054680100000000000000008110156128f157615c46615c308260018794018555846134b3565b81939154905f199060031b92831b921b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c86575f190190615c7682826134b3565b5f1982549160031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d38575f198401848111610602578354935f198501948511610602575f958583615cf597615ce89503615cfb575b505050615c60565b905f5260205260405f2090565b55600190565b615d21615d1b91615d12610700615d2f95886134b3565b928391876134b3565b906134c8565b85905f5260205260405f2090565b555f8080615ce0565b505050505f90565b6126d190615d88615d8294936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190614ba6565b90614ba6565b03601f19810184528361292e565b61028c91615da391615dd3565b90929192615e0d565b90615db682612b1d565b615dc3604051918261292e565b828152601f1961363e8294612b1d565b8151919060418303615e0357615dfc9250602082015190606060408401519301515f1a90615ed4565b9192909190565b50505f9160029190565b615e1681611c46565b80615e1f575050565b615e2881611c46565b60018103615e58577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e6181611c46565b60028103615e9557507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ea1600392611c46565b14615ea95750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f4b579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104f0575f516001600160a01b03811615615f4157905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220d6322a6f79dfa260b6143002f6411140acf47599f685bd2c3432147eed00120464736f6c634300081e0033", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_defaultSigValidator\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"},{\"name\":\"_node\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"DEFAULT_SIG_VALIDATOR\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ESCROW_DEPOSIT_UNLOCK_DELAY\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"MAX_CHALLENGE_DURATION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"MAX_DEPOSIT_ESCROW_STEPS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"MIN_CHALLENGE_DURATION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"NODE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"TRANSFER_GAS_LIMIT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"VALIDATOR_ACTIVATION_DELAY\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"VERSION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"challengeChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengerSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"challengerIdx\",\"type\":\"uint8\",\"internalType\":\"enumParticipantIndex\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"challengeEscrowDeposit\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"challengerSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"challengerIdx\",\"type\":\"uint8\",\"internalType\":\"enumParticipantIndex\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"challengeEscrowWithdrawal\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"challengerSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"challengerIdx\",\"type\":\"uint8\",\"internalType\":\"enumParticipantIndex\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"checkpointChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"claimFunds\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destination\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"closeChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createChannel\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"initState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"depositToChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"depositToNode\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"escrowHead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"finalizeEscrowDeposit\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeEscrowWithdrawal\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeMigration\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getChannelData\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumChannelStatus\"},{\"name\":\"definition\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"lastState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"lockedFunds\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getChannelIds\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEscrowDepositData\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumEscrowStatus\"},{\"name\":\"unlockAt\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"challengeExpiry\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"lockedAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"initState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEscrowDepositIds\",\"inputs\":[{\"name\":\"page\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"pageSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"ids\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEscrowWithdrawalData\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumEscrowStatus\"},{\"name\":\"challengeExpiry\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"lockedAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"initState\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeBalance\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeValidator\",\"inputs\":[{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"outputs\":[{\"name\":\"validator\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"},{\"name\":\"registeredAt\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getOpenChannels\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"openChannels\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getReclaimBalance\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getUnlockableEscrowDepositStats\",\"inputs\":[],\"outputs\":[{\"name\":\"count\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"totalAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initiateEscrowDeposit\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"initiateEscrowWithdrawal\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initiateMigration\",\"inputs\":[{\"name\":\"def\",\"type\":\"tuple\",\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"purgeEscrowDeposits\",\"inputs\":[{\"name\":\"maxSteps\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerNodeValidator\",\"inputs\":[{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"validator\",\"type\":\"address\",\"internalType\":\"contractISignatureValidator\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawFromChannel\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawFromNode\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"ChannelChallenged\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpireAt\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelCheckpointed\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelClosed\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"finalState\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelCreated\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"definition\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structChannelDefinition\",\"components\":[{\"name\":\"challengeDuration\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"node\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nonce\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"approvedSignatureValidators\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"initialState\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelDeposited\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ChannelWithdrawn\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"candidate\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Deposited\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositChallenged\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpireAt\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositFinalized\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositFinalizedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositInitiated\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositInitiatedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowDepositsPurged\",\"inputs\":[{\"name\":\"escrowIds\",\"type\":\"bytes32[]\",\"indexed\":false,\"internalType\":\"bytes32[]\"},{\"name\":\"purgedCount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalChallenged\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"challengeExpireAt\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalFinalized\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalFinalizedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalInitiated\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"EscrowWithdrawalInitiatedOnHome\",\"inputs\":[{\"name\":\"escrowId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FundsClaimed\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"destination\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationInFinalized\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationInInitiated\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationOutFinalized\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MigrationOutInitiated\",\"inputs\":[{\"name\":\"channelId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"state\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structState\",\"components\":[{\"name\":\"version\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"intent\",\"type\":\"uint8\",\"internalType\":\"enumStateIntent\"},{\"name\":\"metadata\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"homeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"nonHomeLedger\",\"type\":\"tuple\",\"internalType\":\"structLedger\",\"components\":[{\"name\":\"chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"userAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"userNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"},{\"name\":\"nodeAllocation\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nodeNetFlow\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"name\":\"userSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"nodeSig\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"NodeBalanceUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TransferFailed\",\"inputs\":[{\"name\":\"recipient\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorId\",\"type\":\"uint8\",\"indexed\":true,\"internalType\":\"uint8\"},{\"name\":\"validator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"contractISignatureValidator\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Withdrawn\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressCollision\",\"inputs\":[{\"name\":\"collision\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ChallengerVersionTooLow\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ECDSAInvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ECDSAInvalidSignatureLength\",\"inputs\":[{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ECDSAInvalidSignatureS\",\"inputs\":[{\"name\":\"s\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"EmptySignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectAmount\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectChallengeDuration\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectChannelId\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectChannelStatus\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectMsgSender\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectNode\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectStateIntent\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectValue\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientBalance\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidValidatorId\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NativeTransferFailed\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NoChannelIdFoundForEscrow\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SafeCastOverflowedIntToUint\",\"inputs\":[{\"name\":\"value\",\"type\":\"int256\",\"internalType\":\"int256\"}]},{\"type\":\"error\",\"name\":\"SafeERC20FailedOperation\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotActive\",\"inputs\":[{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"activatesAt\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotApproved\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorId\",\"type\":\"uint8\",\"internalType\":\"uint8\"}]}]", + Bin: "0x60c03461010b57601f615ee238819003918201601f19168301916001600160401b0383118484101761010f57808492604094855283398101031261010b5780516001600160a01b0381169182820361010b5760200151916001600160a01b0383169081840361010b5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055156100fc57156100fc5760805260a052604051615dbe908161012482396080518181816111910152613ed3015260a051818181610c5c01528181610d790152818161145001528181611a3e0152818161207d0152818161361d015281816140800152818161464901526147510152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806307f241ce1461027f57806316b390b11461027a578063187576d8146102755780633115f6301461027057806338a66be21461026b5780633c684f921461026657806341b660ef1461026157806347de477a1461025c57806351bfcdbd1461025757806353269198146102525780635a0745b41461024d5780635ae2accc146102485780635b9acbf9146102435780635dc46a741461023e5780636840dbd2146102395780636898234b1461023457806371a471411461022f578063735181f01461022a57806382d3e15d146102255780638d0b12a5146102205780638e31c7351461021b57806394191051146102115780639691b46814610216578063a459463114610211578063a5c826801461020c578063b25a1d3814610207578063b65b78d114610202578063b9f4420d146101fd578063c74a2d10146101f8578063c9408398146101f3578063d888ccae146101ee578063d91a1283146101e9578063dc23f29e146101e4578063dd73d494146101df578063e617208c146101da578063f4ac51f5146101d5578063f766f8d6146101d0578063ff5bc09e146101cb5763ffa1ad74146101c6575f80fd5b6126ae565b612697565b612578565b6124fd565b61245f565b6122e5565b61212e565b612012565b611f09565b611c7a565b611bfa565b611bdd565b611aee565b611770565b611611565b6114e7565b611504565b611384565b61123d565b611220565b6111da565b611172565b611093565b61107c565b611031565b610ffb565b610fe0565b610fc4565b610dcc565b610d5a565b610b96565b610870565b6107ad565b610772565b61057b565b6104f5565b610351565b610299565b6001600160a01b0381160361029557565b5f80fd5b34610295576020366003190112610295576001600160a01b036004356102be81610284565b165f526006602052602060405f2054604051908152f35b9181601f84011215610295578235916001600160401b038311610295576020838186019501011161029557565b60643590600282101561029557565b9060606003198301126102955760043591602435906001600160401b03821161029557610340916004016102d5565b909160443560028110156102955790565b34610295576103b36103ed61036536610311565b9294916103c8610380879693965f52600260205260405f2090565b9485549261038f8415156126c9565b600187015460059060081c6001600160a01b031696879260028a01549a8b91613eb2565b9192909901986103c28a6128e3565b87613fe3565b60c06103d3876140d5565b604051809481926301999b9360e61b835260048301612a53565b038173__$682d6198b4eca5bc7e038b912a26498e7e$__5af480156104a9577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80195610461946080945f93610476575b5082610453939461044c896128e3565b908b614149565b01516001600160401b031690565b9061047160405192839283612b8e565b0390a2005b610453935061049c9060c03d60c0116104a2575b610494818361275f565b810190612991565b9261043c565b503d61048a565b612a64565b90602080835192838152019201905f5b8181106104cb5750505090565b82518452602093840193909201916001016104be565b9060206104f29281815201906104ae565b90565b34610295576020366003190112610295576001600160a01b0360043561051a81610284565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b81811061056557610561856105558187038261275f565b604051918291826104e1565b0390f35b825484526020909301926001928301920161053e565b3461029557602036600319011261029557600354600480545f92918390358284111561076c576105ab838561332c565b8082101561075e57506105c28195949392956132ed565b925b80831080610755575b15610748576105e86105de84613145565b90549060031b1c90565b6106036105fd825f52600260205260405f2090565b966139b6565b9561060d81615559565b6107335761061a81615589565b156106e3576001600160a01b036106cb6105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b9d8e9261067f846001600160a01b03165f52600660205260405f2090565b5493610691600483019586549061331f565b9c8d916001600160a01b03165f52600660205260405f2090565b5501805460ff19166003179055565b556106c5828d613339565b526139b6565b604051938452961691602090a25b94939291946105c4565b505050506106f391939250600455565b806106fa57005b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642935261072e60405192839283614462565b0390a1005b505092939491610742906139b6565b926106d9565b50506004559190506106f3565b508185106105cd565b6105c29095949392956132ed565b5f6105ab565b34610295575f366003190112610295576020604051620186a08152f35b6004359060ff8216820361029557565b359060ff8216820361029557565b346102955760203660031901126102955760ff6107c861078f565b165f52600760205260405f2060405160408101918183106001600160401b03841117610826576040928352546001600160a01b03811680835260a09190911c6001600160401b03166020928301819052835191825291810191909152f35b6126de565b90816102609103126102955790565b90600319820160e081126102955760c0136102955760049160c435906001600160401b038211610295576104f29160040161082b565b6108793661083a565b60208101600261088882612bbf565b61089181611d68565b148015610b7b575b8015610b5d575b6108a990612bc9565b60026108b482612bbf565b6108bd81611d68565b03610b4e575b6109a36109016108d33686612c0e565b60c090207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790565b9261092f610920610919865f525f60205260405f2090565b5460ff1690565b610929816123bb565b15612c82565b61093b60208601612c98565b906109458661460e565b610955608087013583838861470f565b60a08161098861098161096a60808401612c98565b6001600160a01b03165f52600660205260405f2090565b5488614776565b604051632a2d120f60e21b8152958692839260048401612ec0565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49081156104a9577fb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e494610a18610a99936001600160a01b03965f91610b1f575b50610a07368b612c0e565b610a113686612fc4565b908a6148c2565b610a3c87610a37866001600160a01b03165f52600160205260405f2090565b61598d565b506002610a4882612bbf565b610a5181611d68565b03610a9e5750857f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a878582613070565b0390a25b604051938493169683613081565b0390a3005b610aa9600391612bbf565b610ab281611d68565b03610aef57857f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610ae78582613070565b0390a2610a8b565b857f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610ae78582613070565b610b41915060a03d60a011610b47575b610b39818361275f565b810190612ca2565b5f6109fc565b503d610b2f565b610b583415612bdf565b6108c3565b506108a9610b6a82612bbf565b610b7381611d68565b1590506108a0565b506003610b8782612bbf565b610b9081611d68565b14610899565b610b9f3661083a565b90610bc06004610bb160208501612bbf565b610bba81611d68565b14612bc9565b610bc98161460e565b610bd66108d33683612c0e565b916080610be560208401612c98565b92013591610bf58382848761470f565b610c19610c0183613110565b85906001600160401b03915f521660205260405f2090565b92610c23856149d5565b15610ca3575050610a997f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f98091610c836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b610c8d3415612bdf565b610c978186614a31565b60405191829182613070565b9091610ccf60c082610cb4876140d5565b604051632ef10bcd60e21b815293849283926004840161311a565b038173__$682d6198b4eca5bc7e038b912a26498e7e$__5af49283156104a9577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7694610a9994610d32935f91610d3b575b50610d2b3686612fc4565b8989614149565b610c9784613194565b610d54915060c03d60c0116104a257610494818361275f565b5f610d20565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b9060406003198301126102955760043591602435906001600160401b038211610295576104f29160040161082b565b3461029557610dda36610d9d565b610deb6009610bb160208401612bbf565b610e076001610e01845f525f60205260405f2090565b016131f8565b610ea2610e1e60208301516001600160a01b031690565b91610e2f608082015184868861470f565b610e393685612fc4565b61014085019386610e4986613110565b6001600160401b031646149586610f5c575b50505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b5489614776565b604051632a2d120f60e21b8152958692839260048401613282565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49182156104a957610ed4935f93610f3b575b50866148c2565b15610f0a576104717f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c39160405191829182613070565b6104717f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c8269160405191829182613070565b610f5591935060a03d60a011610b4757610b39818361275f565b915f610ecd565b610fbb92610f6e610fb6923690612ee5565b6060860152610f803660608b01612ee5565b6080860152610f8d61326e565b60a0860152610f9a61326e565b60c08601526001600160a01b03165f52600160205260405f2090565b615a37565b505f8681610e5b565b34610295575f366003190112610295576020604051612a308152f35b34610295575f36600319011261029557602060405160408152f35b346102955760403660031901126102955761056161101d60243560043561334d565b6040519182916020835260208301906104ae565b346102955761104861104236610d9d565b90613406565b005b6060600319820112610295576004359160243591604435906001600160401b038211610295576104f29160040161082b565b346102955761104861108d3661104a565b91613756565b34610295576020366003190112610295576001600160a01b036004356110b881610284565b165f5260016020526110cc60405f20615901565b5f905f5b815181101561115f576110f76109196110e98385613339565b515f525f60205260405f2090565b611100816123bb565b6003811415908161114a575b5061111a575b6001016110d0565b9161112d818460019310611135576139b6565b929050611112565b61113f8585613339565b516106c58286613339565b60059150611157816123bb565b14155f61110c565b50610561918152604051918291826104e1565b34610295575f3660031901126102955760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b6040906003190112610295576004356111cd81610284565b906024356104f281610284565b346102955760206112176001600160a01b036111f5366111b5565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610295575f366003190112610295576020600454604051908152f35b346102955761124b36610311565b611297611263859493945f52600560205260405f2090565b918254946112728615156126c9565b60a061127d88614c71565b604051809581926312031f5d60e11b8352600483016139c4565b038173__$b69fb814c294bfc16f92e50d7aeced4bde$__5af49081156104a9577fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd8966103c296610461966060965f95611341575b50916113318596610453969385600561131560016113259901546001600160a01b039060081c1690565b97889360028401549a8b91613eb2565b92909193019e8f6128e3565b61133a896128e3565b908b614d2b565b6104539550611325939192966113716113319260a03d60a01161137d575b611369818361275f565b8101906136a5565b965096929193506112eb565b503d61135f565b346102955760603660031901126102955761139d61078f565b6024356113a981610284565b6044356001600160401b038111610295576114bc916113cf6114c19236906004016102d5565b93909461148261147d60ff8316966113e88815156139d5565b6001600160a01b038616986113fe8a15156139eb565b61143f8561143961142d61142d6114208460ff165f52600760205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613a01565b61147761144d8b8730614e62565b917f0000000000000000000000000000000000000000000000000000000000000000933691612f73565b90614e9a565b613a1f565b61149c61148d612780565b6001600160a01b039094168452565b426001600160401b0316602084015260ff165f52600760205260405f2090565b613a35565b7f9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e055f80a3005b34610295575f366003190112610295576020604051620151808152f35b346102955761158d61151536610d9d565b61153661152760208395949501612bbf565b61153081611d68565b15612bc9565b61154c6001610e01855f525f60205260405f2090565b9061157161156460208401516001600160a01b031690565b608084015190838761470f565b60a08161098861158661096a60808401612c98565b5487614776565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104a9577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361047193610c97925f926115f0575b506115e93685612fc4565b90876148c2565b61160a91925060a03d60a011610b4757610b39818361275f565b905f6115de565b346102955761161f3661083a565b906116316006610bb160208501612bbf565b61163a8161460e565b6116476108d33683612c0e565b91608061165660208401612c98565b920135916116668382848761470f565b611672610c0183613110565b9261167c856149d5565b156116b2575050610a9981610c977f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614a31565b90916116ee60a0826116d46116cd61096a6101608401612c98565b5488614cce565b60405162ea54e760e01b815293849283926004840161373f565b038173__$b69fb814c294bfc16f92e50d7aeced4bde$__5af49283156104a9577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f794610a9994610c97935f91611751575b5061174a3686612fc4565b8989614d2b565b61176a915060a03d60a01161137d57611369818361275f565b5f61173f565b6080366003190112610295576004356024356001600160401b0381116102955761179e90369060040161082b565b6044356001600160401b038111610295576117bd9036906004016102d5565b90916117c7610302565b926117d9855f525f60205260405f2090565b6117e5600182016131f8565b936117f1825460ff1690565b906117fb826123bb565b6001821495868015611adb575b61181190612c82565b61181d600585016128e3565b9261185b61182a88613110565b6001600160401b0361185261184688516001600160401b031690565b6001600160401b031690565b91161015613aa3565b60208201516001600160a01b0316978a6080840151956001600160401b036118966118466118888d613110565b93516001600160401b031690565b91161115611a8d57506118eb61192d9493926004926118d660208c01926118d160016118c186612bbf565b6118ca81611d68565b1415612bc9565b6123bb565b80611a6d575b6118e69015612bc9565b612bbf565b6118f481611d68565b1480611a3a575b61190590156131e2565b6119118489898d61470f565b60a08761098861192661096a60808401612c98565b548d614776565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49182156104a9577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a996014996119bb8d8b6119af6119ee9a6119c197611a0c9e6119aa6119d69c6119df9e5f91611a1b575b506119a33688612fc4565b8d896152c4565b613eb2565b93919490923690612fc4565b90613fe3565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b6001600160401b034216613ad9565b9301805467ffffffffffffffff19166001600160401b038516179055565b61047160405192839283613af9565b611a34915060a03d60a011610b4757610b39818361275f565b5f611998565b50337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031614156118fb565b506118e66009611a7c83612bbf565b611a8581611d68565b1490506118dc565b6119d69392506119c19150996014996119bb7f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9c8b6119af6119ee9a6119df9a611a0c9e6119aa3415612bdf565b50611ae5836123bb565b60048314611808565b604036600319011261029557600435611b0681610284565b6001600160a01b0360243591611b1d831515613b19565b611b25615604565b611b30838233615498565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551690815f52600660205260405f205490808201809211611bd8575f516020615d695f395f51905f5291837f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4611bc561047194835f5260066020528460405f2055604051918291829190602083019252565b0390a26040519081529081906020820190565b6132a7565b34610295575f36600319011261029557602060405162093a808152f35b3461029557611c1f611c0b36610d9d565b6115366003610bb160208496959601612bbf565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104a9577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361047193610c97925f926115f057506115e93685612fc4565b34610295575f36600319011261029557600354600454905f805b82841015611d3c577fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b8401545f90815260026020526040902091611cd783615559565b611d2a57611ce483615589565b15611d1357611d0a916004611cfb611d04936139b6565b9401549061331f565b936139b6565b915b9192611c94565b92509250505b604080519182526020820192909252f35b915092611d36906139b6565b91611d0c565b92509050611d19565b634e487b7160e01b5f52602160045260245ffd5b60041115611d6357565b611d45565b600a1115611d6357565b90600a821015611d635752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6104f2916001600160401b038251168152611dc660208301516020830190611d72565b60408201516040820152611e336060830151606083019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b60808281015180516001600160401b031661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611eb460a0840151610260610220850152610260840190611d7f565b92015190610240818403910152611d7f565b92936001600160401b0360c0956104f298979482948752611ee681611d59565b602087015216604085015216606083015260808201528160a08201520190611da3565b3461029557602036600319011261029557600435611f25613b65565b505f52600260205260405f2060405190611f3e826126f2565b80548252610561600182015491611f89611f79611f5b8560ff1690565b94611f6a602088019687613ba9565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b6002810154606085015260038101546001600160401b0380821660808701908152959160401c166001600160401b031660a0820190815291612001611888611fdf600560048501549460c08701958652016128e3565b9360e0810194855251965197611ff489611d59565b516001600160401b031690565b905191519260405196879687611ec6565b346102955760603660031901126102955760043561202f81610284565b5f516020615d695f395f51905f526104716024359261204d84610284565b604435936120656001600160a01b03831615156139eb565b612070851515613b19565b6120a46001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633146131e2565b7f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5611bc58661211e6001600160a01b038516988995865f5260066020526120fb8260405f20546120f682821015613bb5565b61332c565b9788612118836001600160a01b03165f52600660205260405f2090565b556155b8565b6040519081529081906020820190565b346102955761213c3661083a565b61214d6008610bb160208401612bbf565b61215a6108d33684612c0e565b916121bb61216a60208301612c98565b9161217b608082013584868861470f565b6121853685612fc4565b61218e866149d5565b93868515612284575b505060a081610e87610e8061096a60206060850151016001600160a01b0390511690565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49182156104a9576121f8935f9361225f575b506121f2903690612c0e565b866148c2565b1561222e576104717f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a9160405191829182613070565b6104717f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b89160405191829182613070565b6121f291935061227d9060a03d60a011610b4757610b39818361275f565b92906121e6565b610a376122a2926122948661460e565b610f6e366101408b01612ee5565b505f86612197565b9160a0936001600160401b03916104f297969385526122c881611d59565b602085015216604083015260608201528160808201520190611da3565b3461029557602036600319011261029557600435612301613b65565b505f52600560205260405f206040519061231a8261270e565b80548252610561600182015491612351611f7960ff851694602087019561234081611d59565b865260081c6001600160a01b031690565b6002810154606085015260038101546001600160401b03166001600160401b031660808501908152936123aa612395600560048501549460a08501958652016128e3565b9160c0810192835251945195611ff487611d59565b9151905191604051958695866122aa565b60061115611d6357565b906006821015611d635752565b919260a0610120946123eb85612454959a99989a6123c5565b63ffffffff81511660208601526001600160a01b0360208201511660408601526001600160a01b0360408201511660608601526001600160401b036060820151166080860152608081015182860152015160c084015261014060e0840152610140830190611da3565b946101008201520152565b34610295576020366003190112610295576004355f60a060405161248281612729565b82815282602082015282604082015282606082015282608082015201526124a7613b65565b505f525f6020526124ba60405f20613bd7565b80516124c5816123bb565b61056160208301519260408101519060606124ed61184660808401516001600160401b031690565b91015191604051958695866123d2565b61251d61250936610d9d565b6115366002610bb160208496959601612bbf565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104a9577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361047193610c97925f926115f057506115e93685612fc4565b3461029557612586366111b5565b61258e615604565b6001600160a01b038116916125a48315156139eb565b6001600160a01b036125e1826125cb336001600160a01b03165f52600860205260405f2090565b906001600160a01b03165f5260205260405f2090565b54916125ee831515613b19565b5f61260e826125cb336001600160a01b03165f52600860205260405f2090565b551691818361268857612631915f808080858a5af161262b613c34565b50613c63565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a461104860017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b6126929184615662565b612631565b34610295576110486126a83661104a565b91613c8b565b34610295575f36600319011261029557602060405160018152f35b156126d057565b6287a33760e41b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b61010081019081106001600160401b0382111761082657604052565b60e081019081106001600160401b0382111761082657604052565b60c081019081106001600160401b0382111761082657604052565b60a081019081106001600160401b0382111761082657604052565b90601f801991011681019081106001600160401b0382111761082657604052565b6040519061278f60408361275f565b565b6040519061278f60e08361275f565b906040516127ad8161270e565b60c0600482946127ea60ff82546001600160401b03811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c9216801561283d575b602083101461282957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161281e565b5f92918154916128568361280f565b80835292600181169081156128ab575060011461287257505050565b5f9081526020812093945091925b838310612891575060209250010190565b600181602092949394548385870101520191019190612880565b915050602093945060ff929192191683830152151560051b010190565b9061278f6128dc9260405193848092612847565b038361275f565b906040516128f08161270e565b809260ff81546001600160401b038116845260401c1690600a821015611d6357600d6129619160c093602086015260018101546040860152612934600282016127a0565b6060860152612945600782016127a0565b6080860152612956600c82016128c8565b60a0860152016128c8565b910152565b5190600482101561029557565b6001600160401b0381160361029557565b5190811515820361029557565b908160c0910312610295576129f960a0604051926129ae84612729565b80518452602081015160208501526129c860408201612966565b604085015260608101516129db81612973565b606085015260808101516129ee81612973565b608085015201612984565b60a082015290565b908151612a0d81611d59565b815260806001600160401b0381612a33602086015160a0602087015260a0860190611da3565b946040810151604086015282606082015116606086015201511691015290565b9060206104f2928181520190612a01565b6040513d5f823e3d90fd5b90600d6104f292612a9781546001600160401b038116855260ff602086019160401c16611d72565b60018101546040840152612b036060840160028301600460c09160ff8082546001600160401b03811687526001600160a01b038160401c16602088015260e01c161660408501526001810154606085015260028101546080850152600381015460a08501520154910152565b60078101546001600160401b038116610140850152604081901c6001600160a01b031661016085015260e01c60ff1661018084015260088101546101a084015260098101546101c0840152600a8101546101e0840152600b810154610200840152610260610220840152612b7e6102608401600c8301612847565b9261024081850391015201612847565b906001600160401b03612bae602092959495604085526040850190612a6f565b9416910152565b600a111561029557565b356104f281612bb5565b15612bd057565b633226144f60e21b5f5260045ffd5b15612be657565b636956f2ab60e11b5f5260045ffd5b63ffffffff81160361029557565b359061278f82612973565b91908260c091031261029557604051612c2681612729565b60a08082948035612c3681612bf5565b84526020810135612c4681610284565b60208501526040810135612c5981610284565b60408501526060810135612c6c81612973565b6060850152608081013560808501520135910152565b15612c8957565b631e40ad6360e31b5f5260045ffd5b356104f281610284565b908160a09103126102955760405190612cba82612744565b80518252602081015160208301526040810151600681101561029557612cfb9160809160408501526060810151612cf081612973565b606085015201612984565b608082015290565b90612d0f8183516123c5565b60806001600160401b0381612d33602086015160a0602087015260a0860190611da3565b94604081015160408601526060810151606086015201511691015290565b359061278f82612bb5565b60c080916001600160401b038135612d7381612973565b1684526001600160a01b036020820135612d8c81610284565b16602085015260ff612da06040830161079f565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e19823603018112156102955701602081359101916001600160401b03821161029557813603831361029557565b908060209392818452848401375f828201840152601f01601f1916010190565b6104f2916001600160401b038235612e3381612973565b168152612e516020830135612e4781612bb5565b6020830190611d72565b60408201356040820152612e6b6060820160608401612d5c565b612e7d61014082016101408401612d5c565b612eb1612ea5612e91610220850185612dcb565b610260610220860152610260850191612dfc565b92610240810190612dcb565b91610240818503910152612dfc565b9091612ed76104f293604084526040840190612d03565b916020818403910152612e1c565b91908260e091031261029557604051612efd8161270e565b60c08082948035612f0d81612973565b84526020810135612f1d81610284565b6020850152612f2e6040820161079f565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b6001600160401b03811161082657601f01601f191660200190565b929192612f7f82612f58565b91612f8d604051938461275f565b829481845281830111610295578281602093845f960137010152565b9080601f83011215610295578160206104f293359101612f73565b9190916102608184031261029557612fda612791565b92612fe482612c03565b8452612ff260208301612d51565b60208501526040820135604085015261300e8160608401612ee5565b6060850152613021816101408401612ee5565b60808501526102208201356001600160401b0381116102955781613046918401612fa9565b60a08501526102408201356001600160401b038111610295576130699201612fa9565b60c0830152565b9060206104f2928181520190612e1c565b60e09060a06104f2949363ffffffff813561309b81612bf5565b1683526001600160a01b0360208201356130b481610284565b1660208401526001600160a01b0360408201356130d081610284565b1660408401526001600160401b0360608201356130ec81612973565b16606084015260808101356080840152013560a08201528160c08201520190612e1c565b356104f281612973565b9091612ed76104f293604084526040840190612a01565b634e487b7160e01b5f52603260045260245ffd5b60035481101561315d5760035f5260205f2001905f90565b613131565b805482101561315d575f5260205f2001905f90565b916131909183549060031b91821b915f19901b19161790565b9055565b60035468010000000000000000811015610826576001810160035560035481101561315d5760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b156131e957565b6370a8bfcd60e11b5f5260045ffd5b9060405161320581612729565b60a0600382946001600160a01b03815463ffffffff8116865260201c16602085015261325d6001600160401b0360018301546001600160a01b03808216166040880152851c1660608601906001600160401b03169052565b600281015460808501520154910152565b6040519061327d60208361275f565b5f8252565b90916132996104f293604084526040840190612d03565b916020818403910152611da3565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116108265760051b60200190565b604051906132e160208361275f565b5f808352366020840137565b906132f7826132bb565b613304604051918261275f565b8281528092613315601f19916132bb565b0190602036910137565b91908201809211611bd857565b91908203918211611bd857565b805182101561315d5760209160051b010190565b91906003549080840293808504821490151715611bd857818410156133d157830190818411611bd8578082116133c9575b5061339161338c848361332c565b6132ed565b92805b8281106133a057505050565b806133af6105de600193613145565b6133c26133bc858461332c565b88613339565b5201613394565b90505f61337e565b505090506104f26132d2565b906006811015611d635760ff80198354169116179055565b9060206104f2928181520190611da3565b90613418825f525f60205260405f2090565b613424600182016131f8565b91613430825460ff1690565b918461343e600583016128e3565b91600261345560208801516001600160a01b031690565b9561345f816123bb565b148061364e575b6135755750505061347e6001610bb160208401612bbf565b61348e608084015183838761470f565b6134c160a0826134a661098161096a60808401612c98565b604051632a2d120f60e21b8152938492839260048401612ec0565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af480156104a957610fb661354f9461352b88937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613542965f92613554575b506135243689612fc4565b90866148c2565b6001600160a01b03165f52600160205260405f2090565b5060405191829182613070565b0390a2565b61356e91925060a03d60a011610b4757610b39818361275f565b905f613519565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a8955061364192935061354f946135d46014836135bc610fb695600360ff19825416179055565b5f601382015501805467ffffffffffffffff19169055565b61352b60608601613600815160606135f660208301516001600160a01b031690565b9101519085614ae5565b5160a061361760208301516001600160a01b031690565b910151907f0000000000000000000000000000000000000000000000000000000000000000614ae5565b50604051918291826133f5565b506014810154426001600160401b0390911610613466565b1561366d57565b6336c7a86b60e21b5f5260045ffd5b9061368681611d59565b60ff80198354169116179055565b9060206104f2928181520190612a6f565b908160a091031261029557612cfb6080604051926136c284612744565b80518452602081015160208501526136dc60408201612966565b60408501526060810151612cf081612973565b9081516136fb81611d59565b8152608080613719602085015160a0602086015260a0850190611da3565b93604081015160408501526001600160401b036060820151166060850152015191015290565b9091612ed76104f2936040845260408401906136ef565b916137618284614c4f565b61394d57613777825f52600560205260405f2090565b9061378484835414613666565b600182018054929060026137a7600886901c6001600160a01b03165b9560ff1690565b6137b081611d59565b1480613935575b61384e57506002906137d06007610bb160208601612bbf565b0154906137df8284838861470f565b6137ee60a0826116d487614c71565b038173__$b69fb814c294bfc16f92e50d7aeced4bde$__5af49283156104a9577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19461384994610c97935f91611751575061174a3686612fc4565b0390a3565b805460ff191660031790557f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1925060059150600481015f815491556138a0600383016001600160401b03198154169055565b5f516020615d695f395f51905f526001600160a01b036138f36138d1600c8601546001600160a01b039060401c1690565b936138ed856001600160a01b03165f52600660205260405f2090565b5461331f565b9283613910826001600160a01b03165f52600660205260405f2090565b556040519384521691602090a261392561447e565b6138496040519283920182613694565b506003820154426001600160401b03909116106137b7565b613849816139836007610bb160207f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d4989601612bbf565b610c8d613997865f525f60205260405f2090565b600181015460039060201c6001600160a01b031691015490838861470f565b5f198114611bd85760010190565b9060206104f29281815201906136ef565b156139dc57565b6306ee4dcd60e01b5f5260045ffd5b156139f257565b63e6c4247b60e01b5f5260045ffd5b15613a095750565b60ff906357470ffd60e01b5f521660045260245ffd5b15613a2657565b63c1606c2f60e01b5f5260045ffd5b6001600160401b03602061278f93613a7a6001600160a01b0382511685906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0151825467ffffffffffffffff60a01b1916911660a01b67ffffffffffffffff60a01b16179055565b15613aaa57565b637d95736160e01b5f5260045ffd5b6001600160401b0362015180911601906001600160401b038211611bd857565b906001600160401b03809116911601906001600160401b038211611bd857565b906001600160401b03612bae602092959495604085526040850190612e1c565b15613b2057565b6334b2073960e11b5f5260045ffd5b60405190613b3c8261270e565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b728261270e565b606060c0835f81525f60208201525f6040820152613b8e613b2f565b83820152613b9a613b2f565b60808201528260a08201520152565b613bb282611d59565b52565b15613bbc57565b631e9acf1760e31b5f5260045ffd5b6006821015611d635752565b90604051613be481612744565b60806001600160401b0360148395613c0060ff82541686613bcb565b613c0c600182016131f8565b6020860152613c1d600582016128e3565b604086015260138101546060860152015416910152565b3d15613c5e573d90613c4582612f58565b91613c53604051938461275f565b82523d5f602084013e565b606090565b15613c6c575050565b6001600160a01b039063296c17bb60e21b5f521660045260245260445ffd5b91613c9682846156bb565b613e1c57613cac825f52600260205260405f2090565b90613cb984835414613666565b60018201805492906002613cd9600886901c6001600160a01b03166137a0565b613ce281611d59565b1480613df9575b613d7b5750600290613d026005610bb160208601612bbf565b015490613d118284838861470f565b613d2060c082610cb4876140d5565b038173__$682d6198b4eca5bc7e038b912a26498e7e$__5af49283156104a9577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9461384994610c97935f91610d3b5750610d2b3686612fc4565b805460ff191660031790557f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9260059250613df19060048301905f82549255613dda600385016fffffffffffffffff0000000000000000198154169055565b600c84015460401c6001600160a01b031690614ae5565b61392561447e565b50600382015460401c6001600160401b03166001600160401b0342911610613ce9565b613849816139836005610bb160207f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c9601612bbf565b15613e5957565b6306a41ced60e21b5f5260045ffd5b15613e705750565b60ff9063399eb60560e01b5f521660045260245ffd5b15613e8f575050565b9060ff6001600160401b039263975133f360e01b5f52166004521660245260445ffd5b9291908015613f8c57801561315d57613f0191843560f81c9081613f0557507f000000000000000000000000000000000000000000000000000000000000000094600101925f19909201919050565b9091565b600180613f1884613f1f949060ff161c90565b1614613e52565b613f7f613f378260ff165f52600760205260405f2090565b546001600160a01b0381169290613f6c90613f6790613f5884871515613e68565b60a01c6001600160401b031690565b613ab9565b906001600160401b038216421015613e86565b93600101915f1990910190565b63ac241e1160e01b5f5260045ffd5b90816020910312610295575190565b9392606093613fd56001600160a01b0394612bae949998998852608060208901526080880190611d7f565b918683036040880152612dfc565b9193929590613ff1906156d3565b916002821015611d63576020956001600160a01b039261407a5761402d905b604051635850a09b60e11b81529889978896879560048701613faa565b0392165afa80156104a95761278f915f9161404b575b501515613a1f565b61406d915060203d602011614073575b614065818361275f565b810190613f9b565b5f614043565b503d61405b565b5061402d7f0000000000000000000000000000000000000000000000000000000000000000614010565b604051906140b182612744565b5f6080838281526140c0613b65565b60208201528260408201528260608201520152565b6140dd6140a4565b905f5260026020526001600160401b0380600360405f2060ff60018201541661410581611d59565b8552614113600582016128e3565b6020860152600481015460408601520154818116606085015260401c1616608082015290565b600160ff1b8114611bd8575f0390565b936141b694602094939682614166835f52600260205260405f2090565b9860a08701956141768751151590565b156144495760808201518901516001600160a01b0316998a975b60408a018d81516141a081611d59565b6141a981611d59565b61442b575b505051151590565b614418575b50505050506141d460608401516001600160401b031690565b6001600160401b0381166143ef575b5060038601805460808501516001600160401b039081169160401c168190036143b8575b50505f8351135f1461436b576142299061422184516158e5565b92839161548a565b6142386004860191825461331f565b90555b0180515f8113156142d057505f516020615d695f395f51905f52916142686001600160a01b0392516158e5565b6142b960046142928361428c866001600160a01b03165f52600660205260405f2090565b5461332c565b96876142af866001600160a01b03165f52600660205260405f2090565b550191825461331f565b90556040519384521691602090a25b61278f61447e565b90505f81126142e2575b5050506142c8565b5f516020615d695f395f51905f529161430a6143056001600160a01b0393614139565b6158e5565b614355600461432e836138ed866001600160a01b03165f52600660205260405f2090565b968761434b866001600160a01b03165f52600660205260405f2090565b550191825461332c565b90556040519384521691602090a25f80806142da565b6143753415612bdf565b8251905f8212614388575b50505061423b565b61439761430561439f93614139565b928391614ae5565b6143ae6004860191825461332c565b9055825f80614380565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161790555f80614207565b6144129060038801906001600160401b03166001600160401b0319825416179055565b5f6141e3565b614421946157eb565b5f808281806141bb565b600161444292519161443c83611d59565b0161367c565b5f8d6141ae565b600c8b015460401c6001600160a01b0316998a97614190565b9291906144796020916040865260408601906104ae565b930152565b6003546004545f928390828411156145e85761449a838561332c565b806040105f146145da57506144b4604095949392956132ed565b925b808310806145d0575b156145c2576144d06105de84613145565b6144e56105fd825f52600260205260405f2090565b956144ef81615559565b6145ad576144fc81615589565b1561455b576001600160a01b036145436105fd600198999a6106ab955f866106ba610661600c5f516020615d695f395f51905f529a01546001600160a01b039060401c1690565b604051938452961691602090a25b94939291946144b6565b5050509391925061456b90600455565b80614574575050565b81817f8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b364293526145a860405192839283614462565b0390a1565b5050929394916145bc906139b6565b92614551565b509391925061456b90600455565b50604085106144bf565b6144b49095949392956132ed565b5f61449a565b356104f281612bf5565b156145ff57565b630596b15b60e01b5f5260045ffd5b6001600160a01b03602082013561462481610284565b166146308115156139eb565b6001600160a01b03604083013561464681610284565b817f00000000000000000000000000000000000000000000000000000000000000001691829116036146ce5781146146bc5750806201518063ffffffff61468f61278f946145ee565b161015908161469f575b506145f8565b62093a8091506146b363ffffffff916145ee565b1611155f614699565b63abfa558d60e01b5f5260045260245ffd5b6308ad910960e21b5f5260045ffd5b903590601e198136030182121561029557018035906001600160401b0382116102955760200191813603831361029557565b909161278f9361473f61474d926147348361472e6102208901896146dd565b90613eb2565b908888949394615949565b61472e6102408501856146dd565b91937f000000000000000000000000000000000000000000000000000000000000000093615949565b9060146001600160401b039161478a6140a4565b935f525f60205260405f20906147a460ff83541686613bcb565b6147b0600583016128e3565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff81511663ffffffff198554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b1691161784556148b16001850161488461485b60408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b6060830151815467ffffffffffffffff60a01b191660a09190911b67ffffffffffffffff60a01b16179055565b608081015160028501550151910155565b926148fe8161494d946080946148df885f525f60205260405f2090565b976148eb895460ff1690565b6148f4816123bb565b156149c3576152c4565b60408101805161490d816123bb565b614916816123bb565b151580614998575b61497e575b5060148401805460608301516001600160401b03908116911681900361495c575b50500151151590565b6149545750565b60135f910155565b815467ffffffffffffffff19166001600160401b039091161790555f80614944565b614992905161498c816123bb565b856133dd565b5f614923565b50845460ff168151906149aa826123bb565b6149b3826123bb565b6149bc816123bb565b141561491e565b6149d08260018b016147cf565b6152c4565b805f525f60205260ff60405f2054166006811015611d63578015908115614a1d575b50614a18575f525f6020526001600160401b03600760405f20015416461490565b505f90565b60059150614a2a816123bb565b145f6149f7565b90614a8391805f525f602052614a4c600160405f20016131f8565b60a083614a68614a6161096a60808401612c98565b5485614776565b604051632a2d120f60e21b8152968792839260048401612ec0565b038173__$c00a153e45d4e7ce60e0acf48b0547b51a$__5af49283156104a95761278f945f94614ac0575b50614aba903690612fc4565b916148c2565b614aba919450614ade9060a03d60a011610b4757610b39818361275f565b9390614aae565b90614af89291614af3615604565b614b1e565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b9190918115614c4a576001600160a01b0383169283614bc2576001600160a01b038216925f8080808488620186a0f1614b55613c34565b5015614b62575050505050565b614ba5613849926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614bb082825461331f565b90556040519081529081906020820190565b614bd4614bd0848484615add565b1590565b614bdf575b50505050565b81614c286001600160a01b03926125cb7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614c3385825461331f565b90556040519384521691602090a35f808080614bd9565b505050565b905f52600560205260405f2054159081614c67575090565b6104f291506149d5565b614c796140a4565b905f5260056020526001600160401b03600360405f2060ff600182015416614ca081611d59565b8452614cae600582016128e3565b60208501526004810154604085015201541660608201525f608082015290565b90614cd76140a4565b915f5260056020526001600160401b03600360405f2060ff600182015416614cfe81611d59565b8552614d0c600582016128e3565b6020860152600481015460408601520154166060830152608082015290565b6020939291614db691614d46815f52600560205260405f2090565b97604086018051614d5681611d59565b614d5f81611d59565b614e45575b5087856080880194614d768651151590565b614e32575b505050505060038701614d9581546001600160401b031690565b60608601516001600160401b039081169116819003614e1057505051151590565b15614df757608001518201516001600160a01b031680935b8251905f821315614de857614229915061422184516158e5565b5f82126143885750505061423b565b50600c84015460401c6001600160a01b03168093614dce565b815467ffffffffffffffff19166001600160401b039091161790555f806141ae565b614e3b94615b4a565b5f80878582614d7b565b614e5c9051614e5381611d59565b60018b0161367c565b5f614d64565b9160ff6001600160a01b03928360405195466020880152166040860152166060840152166080820152608081526104f260a08261275f565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615036575b806d04ee2d6d415b85acef8100000000600a92101561501a575b662386f26fc10000811015615005575b6305f5e100811015614ff3575b612710811015614fe3575b6064811015614fd4575b1015614fc9575b614f606021614f2860018801615c08565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b908115614f7057614f6090614f2d565b50506001600160a01b03614f9584614f89858498615b9c565b60208151910120615bf2565b911693168314614fc157614fb39181602061142d9351910120615bf2565b14614fbc575f90565b600190565b505050600190565b600190940193614f17565b60029060649004960195614f10565b6004906127109004960195614f06565b6008906305f5e1009004960195614efb565b601090662386f26fc100009004960195614eee565b6020906d04ee2d6d415b85acef81000000009004960195614ede565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104614ec4565b90600a811015611d635768ff000000000000000082549160401b169068ff00000000000000001916179055565b8151815460208401516040808601516001600160401b039094167fffffff000000000000000000000000000000000000000000000000000000000090931692909217911b7bffffffffffffffffffffffffffffffffffffffff0000000000000000161760e09190911b60ff60e01b16178155606082015160018201556080820151600282015560a0820151600382015560c090910151600490910155565b601f821161513657505050565b5f5260205f20906020601f840160051c8301931061516e575b601f0160051c01905b818110615163575050565b5f8155600101615158565b909150819061514f565b91909182516001600160401b0381116108265761519f81615199845461280f565b84615129565b6020601f82116001146151da5781906131909394955f926151cf575b50508160011b915f199060031b1c19161790565b015190505f806151bb565b601f198216906151ed845f5260205f2090565b915f5b8181106152275750958360019596971061520f575b505050811b019055565b01515f1960f88460031b161c191690555f8080615205565b9192602060018192868b0151815501940192016151f0565b8151815467ffffffffffffffff19166001600160401b0391909116178155602082015191600a831015611d635760c0600d9161527e61278f958561505e565b6040810151600185015561529960608201516002860161508b565b6152aa60808201516007860161508b565b6152bb60a0820151600c8601615178565b01519101615178565b9161531360206152e1615305959694965f525f60205260405f2090565b956152f982606086015101516001600160a01b031690565b9586946005890161523f565b01516001600160a01b031690565b5f8351135f1461547b5761532783516158e5565b61533281848461548a565b6153416013870191825461331f565b90555b602083019283515f81136153fa575b5051905f82126153d2575b505050515f8112615375575b50505061278f61447e565b5f516020615d695f395f51905f52916153986143056001600160a01b0393614139565b6153bc601361432e836138ed866001600160a01b03165f52600660205260405f2090565b90556040519384521691602090a25f808061536a565b6143976143056153e193614139565b6153f06013850191825461332c565b9055815f8061535e565b615403906158e5565b6154228161428c866001600160a01b03165f52600660205260405f2090565b908161543f866001600160a01b03165f52600660205260405f2090565b5561544f6013890191825461331f565b90556040519081526001600160a01b038416905f516020615d695f395f51905f5290602090a25f615353565b6154853415612bdf565b615344565b90614af89291615498615604565b908215614c4a576001600160a01b0316918215801561554a576154bc823414612bdf565b156154c657505050565b6001600160a01b03604051926323b872dd60e01b5f52166004523060245260445260205f60648180865af160015f511481161561552b575b6040919091525f606052156155105750565b635274afe760e01b5f526001600160a01b031660045260245ffd5b6001811516615541573d15833b151516166154fe565b503d5f823e3d90fd5b6155543415612bdf565b6154bc565b6001015460ff1661556981611d59565b60038114908115615578575090565b6002915061558581611d59565b1490565b6001600160401b0360038201541642101590816155a4575090565b600180925060ff9101541661558581611d59565b90614af892916155c6615604565b91908115614c4a576001600160a01b031691826155fb5761278f92505f808080856001600160a01b0386165af161262b613c34565b61278f92615662565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146156535760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b633ee5aeb560e01b5f5260045ffd5b916001600160a01b036040519263a9059cbb60e01b5f521660045260245260205f60448180865af160015f51148116156156a5575b604091909152156155105750565b6001811516615541573d15833b15151616615697565b905f52600260205260405f2054159081614c67575090565b6001600160401b03815116906020810151600a811015611d635761577a8260406157da94015161571a60806060840151930151946040519760208901526040880190611d72565b6060860152608085019060c080916001600160401b0381511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b80516001600160401b031661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b61022081526104f26102408261275f565b9190915f52600260205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b602082015193600a851015611d635760c06158e19361584f6002976158979461505e565b6040810151600687015561586a60608201516007880161508b565b61587b6080820151600c880161508b565b61588c60a082015160118801615178565b015160128501615178565b60018301907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b0155565b5f81126158ef5790565b635467221960e11b5f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b81811061593057505061278f9250038361275f565b845483526001948501948794506020909301920161591b565b6001600160a01b039061402d61596f61596a60209895999697993690612fc4565b6156d3565b936040519889978896879563600109bb60e01b875260048701613faa565b6001810190825f528160205260405f2054155f146159f557805468010000000000000000811015610826576159e26159cc826001879401855584613162565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615a23575f190190615a128282613162565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615ad5575f198401848111611bd85783545f19810194908511611bd8575f958583615a9297615a859503615a98575b5050506159fc565b905f5260205260405f2090565b55600190565b615abe615ab891615aaf6105de615acc9588613162565b92839187613162565b90613177565b85905f5260205260405f2090565b555f8080615a7d565b505050505f90565b60405163a9059cbb60e01b60208281019182526001600160a01b03909416602483015260448083019590955293815290925f91615b1b60648261275f565b51908285620186a0f15f51913d91156159f5578115615b415750602011614a1857151590565b9150503b151590565b9190915f52600560205260405f20918255600582019261582b6001600160401b0383511685906001600160401b03166001600160401b0319825416179055565b805191908290602001825e015f815290565b61278f90615be4615bde94936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190615b8a565b90615b8a565b03601f19810184528361275f565b6104f291615bff91615c30565b90929192615c6a565b90615c1282612f58565b615c1f604051918261275f565b8281528092613315601f1991612f58565b8151919060418303615c6057615c599250602082015190606060408401519301515f1a90615ce6565b9192909190565b50505f9160029190565b615c7381611d59565b80615c7c575050565b615c8581611d59565b60018103615c9c5763f645eedf60e01b5f5260045ffd5b615ca581611d59565b60028103615cc0575063fce698f760e01b5f5260045260245ffd5b80615ccc600392611d59565b14615cd45750565b6335e2f38360e21b5f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615d5d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156104a9575f516001600160a01b03811615615d5357905f905f90565b505f906001905f90565b5050505f916003919056fe05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7cea26469706673582212209ca9c46147c1489a82a869c349b42f4d493d4ac3ebf3c0ac4fa5a93e0113024c64736f6c634300081e0033", } // ChannelHubABI is the input ABI used to generate the binding from. @@ -75,7 +75,7 @@ var ChannelHubABI = ChannelHubMetaData.ABI var ChannelHubBin = ChannelHubMetaData.Bin // DeployChannelHub deploys a new Ethereum contract, binding an instance of ChannelHub to it. -func DeployChannelHub(auth *bind.TransactOpts, backend bind.ContractBackend, _defaultSigValidator common.Address) (common.Address, *types.Transaction, *ChannelHub, error) { +func DeployChannelHub(auth *bind.TransactOpts, backend bind.ContractBackend, _defaultSigValidator common.Address, _node common.Address) (common.Address, *types.Transaction, *ChannelHub, error) { parsed, err := ChannelHubMetaData.GetAbi() if err != nil { return common.Address{}, nil, nil, err @@ -84,7 +84,7 @@ func DeployChannelHub(auth *bind.TransactOpts, backend bind.ContractBackend, _de return common.Address{}, nil, nil, errors.New("GetABI returned nil") } - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ChannelHubBin), backend, _defaultSigValidator) + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ChannelHubBin), backend, _defaultSigValidator, _node) if err != nil { return common.Address{}, nil, nil, err } @@ -295,12 +295,12 @@ func (_ChannelHub *ChannelHubCallerSession) ESCROWDEPOSITUNLOCKDELAY() (uint32, return _ChannelHub.Contract.ESCROWDEPOSITUNLOCKDELAY(&_ChannelHub.CallOpts) } -// MAXDEPOSITESCROWPURGE is a free data retrieval call binding the contract method 0x6af820bd. +// MAXCHALLENGEDURATION is a free data retrieval call binding the contract method 0xb9f4420d. // -// Solidity: function MAX_DEPOSIT_ESCROW_PURGE() view returns(uint32) -func (_ChannelHub *ChannelHubCaller) MAXDEPOSITESCROWPURGE(opts *bind.CallOpts) (uint32, error) { +// Solidity: function MAX_CHALLENGE_DURATION() view returns(uint32) +func (_ChannelHub *ChannelHubCaller) MAXCHALLENGEDURATION(opts *bind.CallOpts) (uint32, error) { var out []interface{} - err := _ChannelHub.contract.Call(opts, &out, "MAX_DEPOSIT_ESCROW_PURGE") + err := _ChannelHub.contract.Call(opts, &out, "MAX_CHALLENGE_DURATION") if err != nil { return *new(uint32), err @@ -312,18 +312,49 @@ func (_ChannelHub *ChannelHubCaller) MAXDEPOSITESCROWPURGE(opts *bind.CallOpts) } -// MAXDEPOSITESCROWPURGE is a free data retrieval call binding the contract method 0x6af820bd. +// MAXCHALLENGEDURATION is a free data retrieval call binding the contract method 0xb9f4420d. // -// Solidity: function MAX_DEPOSIT_ESCROW_PURGE() view returns(uint32) -func (_ChannelHub *ChannelHubSession) MAXDEPOSITESCROWPURGE() (uint32, error) { - return _ChannelHub.Contract.MAXDEPOSITESCROWPURGE(&_ChannelHub.CallOpts) +// Solidity: function MAX_CHALLENGE_DURATION() view returns(uint32) +func (_ChannelHub *ChannelHubSession) MAXCHALLENGEDURATION() (uint32, error) { + return _ChannelHub.Contract.MAXCHALLENGEDURATION(&_ChannelHub.CallOpts) } -// MAXDEPOSITESCROWPURGE is a free data retrieval call binding the contract method 0x6af820bd. +// MAXCHALLENGEDURATION is a free data retrieval call binding the contract method 0xb9f4420d. // -// Solidity: function MAX_DEPOSIT_ESCROW_PURGE() view returns(uint32) -func (_ChannelHub *ChannelHubCallerSession) MAXDEPOSITESCROWPURGE() (uint32, error) { - return _ChannelHub.Contract.MAXDEPOSITESCROWPURGE(&_ChannelHub.CallOpts) +// Solidity: function MAX_CHALLENGE_DURATION() view returns(uint32) +func (_ChannelHub *ChannelHubCallerSession) MAXCHALLENGEDURATION() (uint32, error) { + return _ChannelHub.Contract.MAXCHALLENGEDURATION(&_ChannelHub.CallOpts) +} + +// MAXDEPOSITESCROWSTEPS is a free data retrieval call binding the contract method 0x5ae2accc. +// +// Solidity: function MAX_DEPOSIT_ESCROW_STEPS() view returns(uint32) +func (_ChannelHub *ChannelHubCaller) MAXDEPOSITESCROWSTEPS(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _ChannelHub.contract.Call(opts, &out, "MAX_DEPOSIT_ESCROW_STEPS") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// MAXDEPOSITESCROWSTEPS is a free data retrieval call binding the contract method 0x5ae2accc. +// +// Solidity: function MAX_DEPOSIT_ESCROW_STEPS() view returns(uint32) +func (_ChannelHub *ChannelHubSession) MAXDEPOSITESCROWSTEPS() (uint32, error) { + return _ChannelHub.Contract.MAXDEPOSITESCROWSTEPS(&_ChannelHub.CallOpts) +} + +// MAXDEPOSITESCROWSTEPS is a free data retrieval call binding the contract method 0x5ae2accc. +// +// Solidity: function MAX_DEPOSIT_ESCROW_STEPS() view returns(uint32) +func (_ChannelHub *ChannelHubCallerSession) MAXDEPOSITESCROWSTEPS() (uint32, error) { + return _ChannelHub.Contract.MAXDEPOSITESCROWSTEPS(&_ChannelHub.CallOpts) } // MINCHALLENGEDURATION is a free data retrieval call binding the contract method 0x94191051. @@ -357,6 +388,37 @@ func (_ChannelHub *ChannelHubCallerSession) MINCHALLENGEDURATION() (uint32, erro return _ChannelHub.Contract.MINCHALLENGEDURATION(&_ChannelHub.CallOpts) } +// NODE is a free data retrieval call binding the contract method 0x51bfcdbd. +// +// Solidity: function NODE() view returns(address) +func (_ChannelHub *ChannelHubCaller) NODE(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ChannelHub.contract.Call(opts, &out, "NODE") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// NODE is a free data retrieval call binding the contract method 0x51bfcdbd. +// +// Solidity: function NODE() view returns(address) +func (_ChannelHub *ChannelHubSession) NODE() (common.Address, error) { + return _ChannelHub.Contract.NODE(&_ChannelHub.CallOpts) +} + +// NODE is a free data retrieval call binding the contract method 0x51bfcdbd. +// +// Solidity: function NODE() view returns(address) +func (_ChannelHub *ChannelHubCallerSession) NODE() (common.Address, error) { + return _ChannelHub.Contract.NODE(&_ChannelHub.CallOpts) +} + // TRANSFERGASLIMIT is a free data retrieval call binding the contract method 0x38a66be2. // // Solidity: function TRANSFER_GAS_LIMIT() view returns(uint256) @@ -388,6 +450,37 @@ func (_ChannelHub *ChannelHubCallerSession) TRANSFERGASLIMIT() (*big.Int, error) return _ChannelHub.Contract.TRANSFERGASLIMIT(&_ChannelHub.CallOpts) } +// VALIDATORACTIVATIONDELAY is a free data retrieval call binding the contract method 0xa4594631. +// +// Solidity: function VALIDATOR_ACTIVATION_DELAY() view returns(uint64) +func (_ChannelHub *ChannelHubCaller) VALIDATORACTIVATIONDELAY(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _ChannelHub.contract.Call(opts, &out, "VALIDATOR_ACTIVATION_DELAY") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// VALIDATORACTIVATIONDELAY is a free data retrieval call binding the contract method 0xa4594631. +// +// Solidity: function VALIDATOR_ACTIVATION_DELAY() view returns(uint64) +func (_ChannelHub *ChannelHubSession) VALIDATORACTIVATIONDELAY() (uint64, error) { + return _ChannelHub.Contract.VALIDATORACTIVATIONDELAY(&_ChannelHub.CallOpts) +} + +// VALIDATORACTIVATIONDELAY is a free data retrieval call binding the contract method 0xa4594631. +// +// Solidity: function VALIDATOR_ACTIVATION_DELAY() view returns(uint64) +func (_ChannelHub *ChannelHubCallerSession) VALIDATORACTIVATIONDELAY() (uint64, error) { + return _ChannelHub.Contract.VALIDATORACTIVATIONDELAY(&_ChannelHub.CallOpts) +} + // VERSION is a free data retrieval call binding the contract method 0xffa1ad74. // // Solidity: function VERSION() view returns(uint8) @@ -450,37 +543,6 @@ func (_ChannelHub *ChannelHubCallerSession) EscrowHead() (*big.Int, error) { return _ChannelHub.Contract.EscrowHead(&_ChannelHub.CallOpts) } -// GetAccountBalance is a free data retrieval call binding the contract method 0x587675e8. -// -// Solidity: function getAccountBalance(address node, address token) view returns(uint256) -func (_ChannelHub *ChannelHubCaller) GetAccountBalance(opts *bind.CallOpts, node common.Address, token common.Address) (*big.Int, error) { - var out []interface{} - err := _ChannelHub.contract.Call(opts, &out, "getAccountBalance", node, token) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// GetAccountBalance is a free data retrieval call binding the contract method 0x587675e8. -// -// Solidity: function getAccountBalance(address node, address token) view returns(uint256) -func (_ChannelHub *ChannelHubSession) GetAccountBalance(node common.Address, token common.Address) (*big.Int, error) { - return _ChannelHub.Contract.GetAccountBalance(&_ChannelHub.CallOpts, node, token) -} - -// GetAccountBalance is a free data retrieval call binding the contract method 0x587675e8. -// -// Solidity: function getAccountBalance(address node, address token) view returns(uint256) -func (_ChannelHub *ChannelHubCallerSession) GetAccountBalance(node common.Address, token common.Address) (*big.Int, error) { - return _ChannelHub.Contract.GetAccountBalance(&_ChannelHub.CallOpts, node, token) -} - // GetChannelData is a free data retrieval call binding the contract method 0xe617208c. // // Solidity: function getChannelData(bytes32 channelId) view returns(uint8 status, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) lastState, uint256 challengeExpiry, uint256 lockedFunds) @@ -728,35 +790,80 @@ func (_ChannelHub *ChannelHubCallerSession) GetEscrowWithdrawalData(escrowId [32 return _ChannelHub.Contract.GetEscrowWithdrawalData(&_ChannelHub.CallOpts, escrowId) } -// GetNodeValidator is a free data retrieval call binding the contract method 0xb00b6fd6. +// GetNodeBalance is a free data retrieval call binding the contract method 0x07f241ce. // -// Solidity: function getNodeValidator(address node, uint8 validatorId) view returns(address) -func (_ChannelHub *ChannelHubCaller) GetNodeValidator(opts *bind.CallOpts, node common.Address, validatorId uint8) (common.Address, error) { +// Solidity: function getNodeBalance(address token) view returns(uint256) +func (_ChannelHub *ChannelHubCaller) GetNodeBalance(opts *bind.CallOpts, token common.Address) (*big.Int, error) { var out []interface{} - err := _ChannelHub.contract.Call(opts, &out, "getNodeValidator", node, validatorId) + err := _ChannelHub.contract.Call(opts, &out, "getNodeBalance", token) if err != nil { - return *new(common.Address), err + return *new(*big.Int), err } - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) return out0, err } -// GetNodeValidator is a free data retrieval call binding the contract method 0xb00b6fd6. +// GetNodeBalance is a free data retrieval call binding the contract method 0x07f241ce. +// +// Solidity: function getNodeBalance(address token) view returns(uint256) +func (_ChannelHub *ChannelHubSession) GetNodeBalance(token common.Address) (*big.Int, error) { + return _ChannelHub.Contract.GetNodeBalance(&_ChannelHub.CallOpts, token) +} + +// GetNodeBalance is a free data retrieval call binding the contract method 0x07f241ce. // -// Solidity: function getNodeValidator(address node, uint8 validatorId) view returns(address) -func (_ChannelHub *ChannelHubSession) GetNodeValidator(node common.Address, validatorId uint8) (common.Address, error) { - return _ChannelHub.Contract.GetNodeValidator(&_ChannelHub.CallOpts, node, validatorId) +// Solidity: function getNodeBalance(address token) view returns(uint256) +func (_ChannelHub *ChannelHubCallerSession) GetNodeBalance(token common.Address) (*big.Int, error) { + return _ChannelHub.Contract.GetNodeBalance(&_ChannelHub.CallOpts, token) } -// GetNodeValidator is a free data retrieval call binding the contract method 0xb00b6fd6. +// GetNodeValidator is a free data retrieval call binding the contract method 0x3c684f92. // -// Solidity: function getNodeValidator(address node, uint8 validatorId) view returns(address) -func (_ChannelHub *ChannelHubCallerSession) GetNodeValidator(node common.Address, validatorId uint8) (common.Address, error) { - return _ChannelHub.Contract.GetNodeValidator(&_ChannelHub.CallOpts, node, validatorId) +// Solidity: function getNodeValidator(uint8 validatorId) view returns(address validator, uint64 registeredAt) +func (_ChannelHub *ChannelHubCaller) GetNodeValidator(opts *bind.CallOpts, validatorId uint8) (struct { + Validator common.Address + RegisteredAt uint64 +}, error) { + var out []interface{} + err := _ChannelHub.contract.Call(opts, &out, "getNodeValidator", validatorId) + + outstruct := new(struct { + Validator common.Address + RegisteredAt uint64 + }) + if err != nil { + return *outstruct, err + } + + outstruct.Validator = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + outstruct.RegisteredAt = *abi.ConvertType(out[1], new(uint64)).(*uint64) + + return *outstruct, err + +} + +// GetNodeValidator is a free data retrieval call binding the contract method 0x3c684f92. +// +// Solidity: function getNodeValidator(uint8 validatorId) view returns(address validator, uint64 registeredAt) +func (_ChannelHub *ChannelHubSession) GetNodeValidator(validatorId uint8) (struct { + Validator common.Address + RegisteredAt uint64 +}, error) { + return _ChannelHub.Contract.GetNodeValidator(&_ChannelHub.CallOpts, validatorId) +} + +// GetNodeValidator is a free data retrieval call binding the contract method 0x3c684f92. +// +// Solidity: function getNodeValidator(uint8 validatorId) view returns(address validator, uint64 registeredAt) +func (_ChannelHub *ChannelHubCallerSession) GetNodeValidator(validatorId uint8) (struct { + Validator common.Address + RegisteredAt uint64 +}, error) { + return _ChannelHub.Contract.GetNodeValidator(&_ChannelHub.CallOpts, validatorId) } // GetOpenChannels is a free data retrieval call binding the contract method 0x6898234b. @@ -821,6 +928,51 @@ func (_ChannelHub *ChannelHubCallerSession) GetReclaimBalance(account common.Add return _ChannelHub.Contract.GetReclaimBalance(&_ChannelHub.CallOpts, account, token) } +// GetUnlockableEscrowDepositStats is a free data retrieval call binding the contract method 0xc9408398. +// +// Solidity: function getUnlockableEscrowDepositStats() view returns(uint256 count, uint256 totalAmount) +func (_ChannelHub *ChannelHubCaller) GetUnlockableEscrowDepositStats(opts *bind.CallOpts) (struct { + Count *big.Int + TotalAmount *big.Int +}, error) { + var out []interface{} + err := _ChannelHub.contract.Call(opts, &out, "getUnlockableEscrowDepositStats") + + outstruct := new(struct { + Count *big.Int + TotalAmount *big.Int + }) + if err != nil { + return *outstruct, err + } + + outstruct.Count = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.TotalAmount = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +// GetUnlockableEscrowDepositStats is a free data retrieval call binding the contract method 0xc9408398. +// +// Solidity: function getUnlockableEscrowDepositStats() view returns(uint256 count, uint256 totalAmount) +func (_ChannelHub *ChannelHubSession) GetUnlockableEscrowDepositStats() (struct { + Count *big.Int + TotalAmount *big.Int +}, error) { + return _ChannelHub.Contract.GetUnlockableEscrowDepositStats(&_ChannelHub.CallOpts) +} + +// GetUnlockableEscrowDepositStats is a free data retrieval call binding the contract method 0xc9408398. +// +// Solidity: function getUnlockableEscrowDepositStats() view returns(uint256 count, uint256 totalAmount) +func (_ChannelHub *ChannelHubCallerSession) GetUnlockableEscrowDepositStats() (struct { + Count *big.Int + TotalAmount *big.Int +}, error) { + return _ChannelHub.Contract.GetUnlockableEscrowDepositStats(&_ChannelHub.CallOpts) +} + // ChallengeChannel is a paid mutator transaction binding the contract method 0xb25a1d38. // // Solidity: function challengeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate, bytes challengerSig, uint8 challengerIdx) payable returns() @@ -886,21 +1038,21 @@ func (_ChannelHub *ChannelHubTransactorSession) ChallengeEscrowWithdrawal(escrow // CheckpointChannel is a paid mutator transaction binding the contract method 0x9691b468. // -// Solidity: function checkpointChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function checkpointChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubTransactor) CheckpointChannel(opts *bind.TransactOpts, channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.contract.Transact(opts, "checkpointChannel", channelId, candidate) } // CheckpointChannel is a paid mutator transaction binding the contract method 0x9691b468. // -// Solidity: function checkpointChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function checkpointChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubSession) CheckpointChannel(channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.Contract.CheckpointChannel(&_ChannelHub.TransactOpts, channelId, candidate) } // CheckpointChannel is a paid mutator transaction binding the contract method 0x9691b468. // -// Solidity: function checkpointChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function checkpointChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubTransactorSession) CheckpointChannel(channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.Contract.CheckpointChannel(&_ChannelHub.TransactOpts, channelId, candidate) } @@ -928,21 +1080,21 @@ func (_ChannelHub *ChannelHubTransactorSession) ClaimFunds(token common.Address, // CloseChannel is a paid mutator transaction binding the contract method 0x5dc46a74. // -// Solidity: function closeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function closeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubTransactor) CloseChannel(opts *bind.TransactOpts, channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.contract.Transact(opts, "closeChannel", channelId, candidate) } // CloseChannel is a paid mutator transaction binding the contract method 0x5dc46a74. // -// Solidity: function closeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function closeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubSession) CloseChannel(channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.Contract.CloseChannel(&_ChannelHub.TransactOpts, channelId, candidate) } // CloseChannel is a paid mutator transaction binding the contract method 0x5dc46a74. // -// Solidity: function closeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function closeChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubTransactorSession) CloseChannel(channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.Contract.CloseChannel(&_ChannelHub.TransactOpts, channelId, candidate) } @@ -989,67 +1141,67 @@ func (_ChannelHub *ChannelHubTransactorSession) DepositToChannel(channelId [32]b return _ChannelHub.Contract.DepositToChannel(&_ChannelHub.TransactOpts, channelId, candidate) } -// DepositToVault is a paid mutator transaction binding the contract method 0x17536c06. +// DepositToNode is a paid mutator transaction binding the contract method 0xb65b78d1. // -// Solidity: function depositToVault(address node, address token, uint256 amount) payable returns() -func (_ChannelHub *ChannelHubTransactor) DepositToVault(opts *bind.TransactOpts, node common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { - return _ChannelHub.contract.Transact(opts, "depositToVault", node, token, amount) +// Solidity: function depositToNode(address token, uint256 amount) payable returns() +func (_ChannelHub *ChannelHubTransactor) DepositToNode(opts *bind.TransactOpts, token common.Address, amount *big.Int) (*types.Transaction, error) { + return _ChannelHub.contract.Transact(opts, "depositToNode", token, amount) } -// DepositToVault is a paid mutator transaction binding the contract method 0x17536c06. +// DepositToNode is a paid mutator transaction binding the contract method 0xb65b78d1. // -// Solidity: function depositToVault(address node, address token, uint256 amount) payable returns() -func (_ChannelHub *ChannelHubSession) DepositToVault(node common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { - return _ChannelHub.Contract.DepositToVault(&_ChannelHub.TransactOpts, node, token, amount) +// Solidity: function depositToNode(address token, uint256 amount) payable returns() +func (_ChannelHub *ChannelHubSession) DepositToNode(token common.Address, amount *big.Int) (*types.Transaction, error) { + return _ChannelHub.Contract.DepositToNode(&_ChannelHub.TransactOpts, token, amount) } -// DepositToVault is a paid mutator transaction binding the contract method 0x17536c06. +// DepositToNode is a paid mutator transaction binding the contract method 0xb65b78d1. // -// Solidity: function depositToVault(address node, address token, uint256 amount) payable returns() -func (_ChannelHub *ChannelHubTransactorSession) DepositToVault(node common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { - return _ChannelHub.Contract.DepositToVault(&_ChannelHub.TransactOpts, node, token, amount) +// Solidity: function depositToNode(address token, uint256 amount) payable returns() +func (_ChannelHub *ChannelHubTransactorSession) DepositToNode(token common.Address, amount *big.Int) (*types.Transaction, error) { + return _ChannelHub.Contract.DepositToNode(&_ChannelHub.TransactOpts, token, amount) } -// FinalizeEscrowDeposit is a paid mutator transaction binding the contract method 0x13c380ed. +// FinalizeEscrowDeposit is a paid mutator transaction binding the contract method 0xff5bc09e. // -// Solidity: function finalizeEscrowDeposit(bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() -func (_ChannelHub *ChannelHubTransactor) FinalizeEscrowDeposit(opts *bind.TransactOpts, escrowId [32]byte, candidate State) (*types.Transaction, error) { - return _ChannelHub.contract.Transact(opts, "finalizeEscrowDeposit", escrowId, candidate) +// Solidity: function finalizeEscrowDeposit(bytes32 channelId, bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() +func (_ChannelHub *ChannelHubTransactor) FinalizeEscrowDeposit(opts *bind.TransactOpts, channelId [32]byte, escrowId [32]byte, candidate State) (*types.Transaction, error) { + return _ChannelHub.contract.Transact(opts, "finalizeEscrowDeposit", channelId, escrowId, candidate) } -// FinalizeEscrowDeposit is a paid mutator transaction binding the contract method 0x13c380ed. +// FinalizeEscrowDeposit is a paid mutator transaction binding the contract method 0xff5bc09e. // -// Solidity: function finalizeEscrowDeposit(bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() -func (_ChannelHub *ChannelHubSession) FinalizeEscrowDeposit(escrowId [32]byte, candidate State) (*types.Transaction, error) { - return _ChannelHub.Contract.FinalizeEscrowDeposit(&_ChannelHub.TransactOpts, escrowId, candidate) +// Solidity: function finalizeEscrowDeposit(bytes32 channelId, bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() +func (_ChannelHub *ChannelHubSession) FinalizeEscrowDeposit(channelId [32]byte, escrowId [32]byte, candidate State) (*types.Transaction, error) { + return _ChannelHub.Contract.FinalizeEscrowDeposit(&_ChannelHub.TransactOpts, channelId, escrowId, candidate) } -// FinalizeEscrowDeposit is a paid mutator transaction binding the contract method 0x13c380ed. +// FinalizeEscrowDeposit is a paid mutator transaction binding the contract method 0xff5bc09e. // -// Solidity: function finalizeEscrowDeposit(bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() -func (_ChannelHub *ChannelHubTransactorSession) FinalizeEscrowDeposit(escrowId [32]byte, candidate State) (*types.Transaction, error) { - return _ChannelHub.Contract.FinalizeEscrowDeposit(&_ChannelHub.TransactOpts, escrowId, candidate) +// Solidity: function finalizeEscrowDeposit(bytes32 channelId, bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() +func (_ChannelHub *ChannelHubTransactorSession) FinalizeEscrowDeposit(channelId [32]byte, escrowId [32]byte, candidate State) (*types.Transaction, error) { + return _ChannelHub.Contract.FinalizeEscrowDeposit(&_ChannelHub.TransactOpts, channelId, escrowId, candidate) } -// FinalizeEscrowWithdrawal is a paid mutator transaction binding the contract method 0x7e7985f9. +// FinalizeEscrowWithdrawal is a paid mutator transaction binding the contract method 0x6840dbd2. // -// Solidity: function finalizeEscrowWithdrawal(bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() -func (_ChannelHub *ChannelHubTransactor) FinalizeEscrowWithdrawal(opts *bind.TransactOpts, escrowId [32]byte, candidate State) (*types.Transaction, error) { - return _ChannelHub.contract.Transact(opts, "finalizeEscrowWithdrawal", escrowId, candidate) +// Solidity: function finalizeEscrowWithdrawal(bytes32 channelId, bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() +func (_ChannelHub *ChannelHubTransactor) FinalizeEscrowWithdrawal(opts *bind.TransactOpts, channelId [32]byte, escrowId [32]byte, candidate State) (*types.Transaction, error) { + return _ChannelHub.contract.Transact(opts, "finalizeEscrowWithdrawal", channelId, escrowId, candidate) } -// FinalizeEscrowWithdrawal is a paid mutator transaction binding the contract method 0x7e7985f9. +// FinalizeEscrowWithdrawal is a paid mutator transaction binding the contract method 0x6840dbd2. // -// Solidity: function finalizeEscrowWithdrawal(bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() -func (_ChannelHub *ChannelHubSession) FinalizeEscrowWithdrawal(escrowId [32]byte, candidate State) (*types.Transaction, error) { - return _ChannelHub.Contract.FinalizeEscrowWithdrawal(&_ChannelHub.TransactOpts, escrowId, candidate) +// Solidity: function finalizeEscrowWithdrawal(bytes32 channelId, bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() +func (_ChannelHub *ChannelHubSession) FinalizeEscrowWithdrawal(channelId [32]byte, escrowId [32]byte, candidate State) (*types.Transaction, error) { + return _ChannelHub.Contract.FinalizeEscrowWithdrawal(&_ChannelHub.TransactOpts, channelId, escrowId, candidate) } -// FinalizeEscrowWithdrawal is a paid mutator transaction binding the contract method 0x7e7985f9. +// FinalizeEscrowWithdrawal is a paid mutator transaction binding the contract method 0x6840dbd2. // -// Solidity: function finalizeEscrowWithdrawal(bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() -func (_ChannelHub *ChannelHubTransactorSession) FinalizeEscrowWithdrawal(escrowId [32]byte, candidate State) (*types.Transaction, error) { - return _ChannelHub.Contract.FinalizeEscrowWithdrawal(&_ChannelHub.TransactOpts, escrowId, candidate) +// Solidity: function finalizeEscrowWithdrawal(bytes32 channelId, bytes32 escrowId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() +func (_ChannelHub *ChannelHubTransactorSession) FinalizeEscrowWithdrawal(channelId [32]byte, escrowId [32]byte, candidate State) (*types.Transaction, error) { + return _ChannelHub.Contract.FinalizeEscrowWithdrawal(&_ChannelHub.TransactOpts, channelId, escrowId, candidate) } // FinalizeMigration is a paid mutator transaction binding the contract method 0x53269198. @@ -1138,86 +1290,86 @@ func (_ChannelHub *ChannelHubTransactorSession) InitiateMigration(def ChannelDef // PurgeEscrowDeposits is a paid mutator transaction binding the contract method 0x3115f630. // -// Solidity: function purgeEscrowDeposits(uint256 maxToPurge) returns() -func (_ChannelHub *ChannelHubTransactor) PurgeEscrowDeposits(opts *bind.TransactOpts, maxToPurge *big.Int) (*types.Transaction, error) { - return _ChannelHub.contract.Transact(opts, "purgeEscrowDeposits", maxToPurge) +// Solidity: function purgeEscrowDeposits(uint256 maxSteps) returns() +func (_ChannelHub *ChannelHubTransactor) PurgeEscrowDeposits(opts *bind.TransactOpts, maxSteps *big.Int) (*types.Transaction, error) { + return _ChannelHub.contract.Transact(opts, "purgeEscrowDeposits", maxSteps) } // PurgeEscrowDeposits is a paid mutator transaction binding the contract method 0x3115f630. // -// Solidity: function purgeEscrowDeposits(uint256 maxToPurge) returns() -func (_ChannelHub *ChannelHubSession) PurgeEscrowDeposits(maxToPurge *big.Int) (*types.Transaction, error) { - return _ChannelHub.Contract.PurgeEscrowDeposits(&_ChannelHub.TransactOpts, maxToPurge) +// Solidity: function purgeEscrowDeposits(uint256 maxSteps) returns() +func (_ChannelHub *ChannelHubSession) PurgeEscrowDeposits(maxSteps *big.Int) (*types.Transaction, error) { + return _ChannelHub.Contract.PurgeEscrowDeposits(&_ChannelHub.TransactOpts, maxSteps) } // PurgeEscrowDeposits is a paid mutator transaction binding the contract method 0x3115f630. // -// Solidity: function purgeEscrowDeposits(uint256 maxToPurge) returns() -func (_ChannelHub *ChannelHubTransactorSession) PurgeEscrowDeposits(maxToPurge *big.Int) (*types.Transaction, error) { - return _ChannelHub.Contract.PurgeEscrowDeposits(&_ChannelHub.TransactOpts, maxToPurge) +// Solidity: function purgeEscrowDeposits(uint256 maxSteps) returns() +func (_ChannelHub *ChannelHubTransactorSession) PurgeEscrowDeposits(maxSteps *big.Int) (*types.Transaction, error) { + return _ChannelHub.Contract.PurgeEscrowDeposits(&_ChannelHub.TransactOpts, maxSteps) } -// RegisterNodeValidator is a paid mutator transaction binding the contract method 0xbeed9d5f. +// RegisterNodeValidator is a paid mutator transaction binding the contract method 0x8e31c735. // -// Solidity: function registerNodeValidator(address node, uint8 validatorId, address validator, bytes signature) returns() -func (_ChannelHub *ChannelHubTransactor) RegisterNodeValidator(opts *bind.TransactOpts, node common.Address, validatorId uint8, validator common.Address, signature []byte) (*types.Transaction, error) { - return _ChannelHub.contract.Transact(opts, "registerNodeValidator", node, validatorId, validator, signature) +// Solidity: function registerNodeValidator(uint8 validatorId, address validator, bytes signature) returns() +func (_ChannelHub *ChannelHubTransactor) RegisterNodeValidator(opts *bind.TransactOpts, validatorId uint8, validator common.Address, signature []byte) (*types.Transaction, error) { + return _ChannelHub.contract.Transact(opts, "registerNodeValidator", validatorId, validator, signature) } -// RegisterNodeValidator is a paid mutator transaction binding the contract method 0xbeed9d5f. +// RegisterNodeValidator is a paid mutator transaction binding the contract method 0x8e31c735. // -// Solidity: function registerNodeValidator(address node, uint8 validatorId, address validator, bytes signature) returns() -func (_ChannelHub *ChannelHubSession) RegisterNodeValidator(node common.Address, validatorId uint8, validator common.Address, signature []byte) (*types.Transaction, error) { - return _ChannelHub.Contract.RegisterNodeValidator(&_ChannelHub.TransactOpts, node, validatorId, validator, signature) +// Solidity: function registerNodeValidator(uint8 validatorId, address validator, bytes signature) returns() +func (_ChannelHub *ChannelHubSession) RegisterNodeValidator(validatorId uint8, validator common.Address, signature []byte) (*types.Transaction, error) { + return _ChannelHub.Contract.RegisterNodeValidator(&_ChannelHub.TransactOpts, validatorId, validator, signature) } -// RegisterNodeValidator is a paid mutator transaction binding the contract method 0xbeed9d5f. +// RegisterNodeValidator is a paid mutator transaction binding the contract method 0x8e31c735. // -// Solidity: function registerNodeValidator(address node, uint8 validatorId, address validator, bytes signature) returns() -func (_ChannelHub *ChannelHubTransactorSession) RegisterNodeValidator(node common.Address, validatorId uint8, validator common.Address, signature []byte) (*types.Transaction, error) { - return _ChannelHub.Contract.RegisterNodeValidator(&_ChannelHub.TransactOpts, node, validatorId, validator, signature) +// Solidity: function registerNodeValidator(uint8 validatorId, address validator, bytes signature) returns() +func (_ChannelHub *ChannelHubTransactorSession) RegisterNodeValidator(validatorId uint8, validator common.Address, signature []byte) (*types.Transaction, error) { + return _ChannelHub.Contract.RegisterNodeValidator(&_ChannelHub.TransactOpts, validatorId, validator, signature) } // WithdrawFromChannel is a paid mutator transaction binding the contract method 0xc74a2d10. // -// Solidity: function withdrawFromChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function withdrawFromChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubTransactor) WithdrawFromChannel(opts *bind.TransactOpts, channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.contract.Transact(opts, "withdrawFromChannel", channelId, candidate) } // WithdrawFromChannel is a paid mutator transaction binding the contract method 0xc74a2d10. // -// Solidity: function withdrawFromChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function withdrawFromChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubSession) WithdrawFromChannel(channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.Contract.WithdrawFromChannel(&_ChannelHub.TransactOpts, channelId, candidate) } // WithdrawFromChannel is a paid mutator transaction binding the contract method 0xc74a2d10. // -// Solidity: function withdrawFromChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) payable returns() +// Solidity: function withdrawFromChannel(bytes32 channelId, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) candidate) returns() func (_ChannelHub *ChannelHubTransactorSession) WithdrawFromChannel(channelId [32]byte, candidate State) (*types.Transaction, error) { return _ChannelHub.Contract.WithdrawFromChannel(&_ChannelHub.TransactOpts, channelId, candidate) } -// WithdrawFromVault is a paid mutator transaction binding the contract method 0xecf3d7e8. +// WithdrawFromNode is a paid mutator transaction binding the contract method 0xd91a1283. // -// Solidity: function withdrawFromVault(address to, address token, uint256 amount) returns() -func (_ChannelHub *ChannelHubTransactor) WithdrawFromVault(opts *bind.TransactOpts, to common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { - return _ChannelHub.contract.Transact(opts, "withdrawFromVault", to, token, amount) +// Solidity: function withdrawFromNode(address to, address token, uint256 amount) returns() +func (_ChannelHub *ChannelHubTransactor) WithdrawFromNode(opts *bind.TransactOpts, to common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { + return _ChannelHub.contract.Transact(opts, "withdrawFromNode", to, token, amount) } -// WithdrawFromVault is a paid mutator transaction binding the contract method 0xecf3d7e8. +// WithdrawFromNode is a paid mutator transaction binding the contract method 0xd91a1283. // -// Solidity: function withdrawFromVault(address to, address token, uint256 amount) returns() -func (_ChannelHub *ChannelHubSession) WithdrawFromVault(to common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { - return _ChannelHub.Contract.WithdrawFromVault(&_ChannelHub.TransactOpts, to, token, amount) +// Solidity: function withdrawFromNode(address to, address token, uint256 amount) returns() +func (_ChannelHub *ChannelHubSession) WithdrawFromNode(to common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { + return _ChannelHub.Contract.WithdrawFromNode(&_ChannelHub.TransactOpts, to, token, amount) } -// WithdrawFromVault is a paid mutator transaction binding the contract method 0xecf3d7e8. +// WithdrawFromNode is a paid mutator transaction binding the contract method 0xd91a1283. // -// Solidity: function withdrawFromVault(address to, address token, uint256 amount) returns() -func (_ChannelHub *ChannelHubTransactorSession) WithdrawFromVault(to common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { - return _ChannelHub.Contract.WithdrawFromVault(&_ChannelHub.TransactOpts, to, token, amount) +// Solidity: function withdrawFromNode(address to, address token, uint256 amount) returns() +func (_ChannelHub *ChannelHubTransactorSession) WithdrawFromNode(to common.Address, token common.Address, amount *big.Int) (*types.Transaction, error) { + return _ChannelHub.Contract.WithdrawFromNode(&_ChannelHub.TransactOpts, to, token, amount) } // ChannelHubChannelChallengedIterator is returned from FilterChannelChallenged and is used to iterate over the raw logs and unpacked data for ChannelChallenged events raised by the ChannelHub contract. @@ -1727,16 +1879,15 @@ func (it *ChannelHubChannelCreatedIterator) Close() error { type ChannelHubChannelCreated struct { ChannelId [32]byte User common.Address - Node common.Address Definition ChannelDefinition InitialState State Raw types.Log // Blockchain specific contextual infos } -// FilterChannelCreated is a free log retrieval operation binding the contract event 0xb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b4. +// FilterChannelCreated is a free log retrieval operation binding the contract event 0xb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e4. // -// Solidity: event ChannelCreated(bytes32 indexed channelId, address indexed user, address indexed node, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) initialState) -func (_ChannelHub *ChannelHubFilterer) FilterChannelCreated(opts *bind.FilterOpts, channelId [][32]byte, user []common.Address, node []common.Address) (*ChannelHubChannelCreatedIterator, error) { +// Solidity: event ChannelCreated(bytes32 indexed channelId, address indexed user, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) initialState) +func (_ChannelHub *ChannelHubFilterer) FilterChannelCreated(opts *bind.FilterOpts, channelId [][32]byte, user []common.Address) (*ChannelHubChannelCreatedIterator, error) { var channelIdRule []interface{} for _, channelIdItem := range channelId { @@ -1746,22 +1897,18 @@ func (_ChannelHub *ChannelHubFilterer) FilterChannelCreated(opts *bind.FilterOpt for _, userItem := range user { userRule = append(userRule, userItem) } - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "ChannelCreated", channelIdRule, userRule, nodeRule) + logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "ChannelCreated", channelIdRule, userRule) if err != nil { return nil, err } return &ChannelHubChannelCreatedIterator{contract: _ChannelHub.contract, event: "ChannelCreated", logs: logs, sub: sub}, nil } -// WatchChannelCreated is a free log subscription operation binding the contract event 0xb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b4. +// WatchChannelCreated is a free log subscription operation binding the contract event 0xb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e4. // -// Solidity: event ChannelCreated(bytes32 indexed channelId, address indexed user, address indexed node, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) initialState) -func (_ChannelHub *ChannelHubFilterer) WatchChannelCreated(opts *bind.WatchOpts, sink chan<- *ChannelHubChannelCreated, channelId [][32]byte, user []common.Address, node []common.Address) (event.Subscription, error) { +// Solidity: event ChannelCreated(bytes32 indexed channelId, address indexed user, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) initialState) +func (_ChannelHub *ChannelHubFilterer) WatchChannelCreated(opts *bind.WatchOpts, sink chan<- *ChannelHubChannelCreated, channelId [][32]byte, user []common.Address) (event.Subscription, error) { var channelIdRule []interface{} for _, channelIdItem := range channelId { @@ -1771,12 +1918,8 @@ func (_ChannelHub *ChannelHubFilterer) WatchChannelCreated(opts *bind.WatchOpts, for _, userItem := range user { userRule = append(userRule, userItem) } - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "ChannelCreated", channelIdRule, userRule, nodeRule) + logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "ChannelCreated", channelIdRule, userRule) if err != nil { return nil, err } @@ -1808,9 +1951,9 @@ func (_ChannelHub *ChannelHubFilterer) WatchChannelCreated(opts *bind.WatchOpts, }), nil } -// ParseChannelCreated is a log parse operation binding the contract event 0xb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b4. +// ParseChannelCreated is a log parse operation binding the contract event 0xb0d099feaab5034d04a1c610e86b8832343f2127b3c667b705834dafdf96e9e4. // -// Solidity: event ChannelCreated(bytes32 indexed channelId, address indexed user, address indexed node, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) initialState) +// Solidity: event ChannelCreated(bytes32 indexed channelId, address indexed user, (uint32,address,address,uint64,uint256,bytes32) definition, (uint64,uint8,bytes32,(uint64,address,uint8,uint256,int256,uint256,int256),(uint64,address,uint8,uint256,int256,uint256,int256),bytes,bytes) initialState) func (_ChannelHub *ChannelHubFilterer) ParseChannelCreated(log types.Log) (*ChannelHubChannelCreated, error) { event := new(ChannelHubChannelCreated) if err := _ChannelHub.contract.UnpackLog(event, "ChannelCreated", log); err != nil { @@ -2179,48 +2322,39 @@ func (it *ChannelHubDepositedIterator) Close() error { // ChannelHubDeposited represents a Deposited event raised by the ChannelHub contract. type ChannelHubDeposited struct { - Wallet common.Address Token common.Address Amount *big.Int Raw types.Log // Blockchain specific contextual infos } -// FilterDeposited is a free log retrieval operation binding the contract event 0x8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7. +// FilterDeposited is a free log retrieval operation binding the contract event 0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4. // -// Solidity: event Deposited(address indexed wallet, address indexed token, uint256 amount) -func (_ChannelHub *ChannelHubFilterer) FilterDeposited(opts *bind.FilterOpts, wallet []common.Address, token []common.Address) (*ChannelHubDepositedIterator, error) { +// Solidity: event Deposited(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) FilterDeposited(opts *bind.FilterOpts, token []common.Address) (*ChannelHubDepositedIterator, error) { - var walletRule []interface{} - for _, walletItem := range wallet { - walletRule = append(walletRule, walletItem) - } var tokenRule []interface{} for _, tokenItem := range token { tokenRule = append(tokenRule, tokenItem) } - logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "Deposited", walletRule, tokenRule) + logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "Deposited", tokenRule) if err != nil { return nil, err } return &ChannelHubDepositedIterator{contract: _ChannelHub.contract, event: "Deposited", logs: logs, sub: sub}, nil } -// WatchDeposited is a free log subscription operation binding the contract event 0x8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7. +// WatchDeposited is a free log subscription operation binding the contract event 0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4. // -// Solidity: event Deposited(address indexed wallet, address indexed token, uint256 amount) -func (_ChannelHub *ChannelHubFilterer) WatchDeposited(opts *bind.WatchOpts, sink chan<- *ChannelHubDeposited, wallet []common.Address, token []common.Address) (event.Subscription, error) { +// Solidity: event Deposited(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) WatchDeposited(opts *bind.WatchOpts, sink chan<- *ChannelHubDeposited, token []common.Address) (event.Subscription, error) { - var walletRule []interface{} - for _, walletItem := range wallet { - walletRule = append(walletRule, walletItem) - } var tokenRule []interface{} for _, tokenItem := range token { tokenRule = append(tokenRule, tokenItem) } - logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "Deposited", walletRule, tokenRule) + logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "Deposited", tokenRule) if err != nil { return nil, err } @@ -2252,9 +2386,9 @@ func (_ChannelHub *ChannelHubFilterer) WatchDeposited(opts *bind.WatchOpts, sink }), nil } -// ParseDeposited is a log parse operation binding the contract event 0x8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7. +// ParseDeposited is a log parse operation binding the contract event 0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4. // -// Solidity: event Deposited(address indexed wallet, address indexed token, uint256 amount) +// Solidity: event Deposited(address indexed token, uint256 amount) func (_ChannelHub *ChannelHubFilterer) ParseDeposited(log types.Log) (*ChannelHubDeposited, error) { event := new(ChannelHubDeposited) if err := _ChannelHub.contract.UnpackLog(event, "Deposited", log); err != nil { @@ -3095,13 +3229,14 @@ func (it *ChannelHubEscrowDepositsPurgedIterator) Close() error { // ChannelHubEscrowDepositsPurged represents a EscrowDepositsPurged event raised by the ChannelHub contract. type ChannelHubEscrowDepositsPurged struct { + EscrowIds [][32]byte PurgedCount *big.Int Raw types.Log // Blockchain specific contextual infos } -// FilterEscrowDepositsPurged is a free log retrieval operation binding the contract event 0x61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd145. +// FilterEscrowDepositsPurged is a free log retrieval operation binding the contract event 0x8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642. // -// Solidity: event EscrowDepositsPurged(uint256 purgedCount) +// Solidity: event EscrowDepositsPurged(bytes32[] escrowIds, uint256 purgedCount) func (_ChannelHub *ChannelHubFilterer) FilterEscrowDepositsPurged(opts *bind.FilterOpts) (*ChannelHubEscrowDepositsPurgedIterator, error) { logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "EscrowDepositsPurged") @@ -3111,9 +3246,9 @@ func (_ChannelHub *ChannelHubFilterer) FilterEscrowDepositsPurged(opts *bind.Fil return &ChannelHubEscrowDepositsPurgedIterator{contract: _ChannelHub.contract, event: "EscrowDepositsPurged", logs: logs, sub: sub}, nil } -// WatchEscrowDepositsPurged is a free log subscription operation binding the contract event 0x61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd145. +// WatchEscrowDepositsPurged is a free log subscription operation binding the contract event 0x8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642. // -// Solidity: event EscrowDepositsPurged(uint256 purgedCount) +// Solidity: event EscrowDepositsPurged(bytes32[] escrowIds, uint256 purgedCount) func (_ChannelHub *ChannelHubFilterer) WatchEscrowDepositsPurged(opts *bind.WatchOpts, sink chan<- *ChannelHubEscrowDepositsPurged) (event.Subscription, error) { logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "EscrowDepositsPurged") @@ -3148,9 +3283,9 @@ func (_ChannelHub *ChannelHubFilterer) WatchEscrowDepositsPurged(opts *bind.Watc }), nil } -// ParseEscrowDepositsPurged is a log parse operation binding the contract event 0x61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd145. +// ParseEscrowDepositsPurged is a log parse operation binding the contract event 0x8fac6141d748dc9c9bc16cc25f636385597618190a44c03d33be5656e01b3642. // -// Solidity: event EscrowDepositsPurged(uint256 purgedCount) +// Solidity: event EscrowDepositsPurged(bytes32[] escrowIds, uint256 purgedCount) func (_ChannelHub *ChannelHubFilterer) ParseEscrowDepositsPurged(log types.Log) (*ChannelHubEscrowDepositsPurged, error) { event := new(ChannelHubEscrowDepositsPurged) if err := _ChannelHub.contract.UnpackLog(event, "EscrowDepositsPurged", log); err != nil { @@ -4665,6 +4800,151 @@ func (_ChannelHub *ChannelHubFilterer) ParseMigrationOutInitiated(log types.Log) return event, nil } +// ChannelHubNodeBalanceUpdatedIterator is returned from FilterNodeBalanceUpdated and is used to iterate over the raw logs and unpacked data for NodeBalanceUpdated events raised by the ChannelHub contract. +type ChannelHubNodeBalanceUpdatedIterator struct { + Event *ChannelHubNodeBalanceUpdated // 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 *ChannelHubNodeBalanceUpdatedIterator) 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(ChannelHubNodeBalanceUpdated) + 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(ChannelHubNodeBalanceUpdated) + 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 *ChannelHubNodeBalanceUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ChannelHubNodeBalanceUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ChannelHubNodeBalanceUpdated represents a NodeBalanceUpdated event raised by the ChannelHub contract. +type ChannelHubNodeBalanceUpdated struct { + Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNodeBalanceUpdated is a free log retrieval operation binding the contract event 0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce. +// +// Solidity: event NodeBalanceUpdated(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) FilterNodeBalanceUpdated(opts *bind.FilterOpts, token []common.Address) (*ChannelHubNodeBalanceUpdatedIterator, error) { + + var tokenRule []interface{} + for _, tokenItem := range token { + tokenRule = append(tokenRule, tokenItem) + } + + logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "NodeBalanceUpdated", tokenRule) + if err != nil { + return nil, err + } + return &ChannelHubNodeBalanceUpdatedIterator{contract: _ChannelHub.contract, event: "NodeBalanceUpdated", logs: logs, sub: sub}, nil +} + +// WatchNodeBalanceUpdated is a free log subscription operation binding the contract event 0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce. +// +// Solidity: event NodeBalanceUpdated(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) WatchNodeBalanceUpdated(opts *bind.WatchOpts, sink chan<- *ChannelHubNodeBalanceUpdated, token []common.Address) (event.Subscription, error) { + + var tokenRule []interface{} + for _, tokenItem := range token { + tokenRule = append(tokenRule, tokenItem) + } + + logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "NodeBalanceUpdated", tokenRule) + 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(ChannelHubNodeBalanceUpdated) + if err := _ChannelHub.contract.UnpackLog(event, "NodeBalanceUpdated", 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 +} + +// ParseNodeBalanceUpdated is a log parse operation binding the contract event 0x05f47829691a1f710b0620aedd52749bb09d8abe4bb530d306db920a71b0d7ce. +// +// Solidity: event NodeBalanceUpdated(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) ParseNodeBalanceUpdated(log types.Log) (*ChannelHubNodeBalanceUpdated, error) { + event := new(ChannelHubNodeBalanceUpdated) + if err := _ChannelHub.contract.UnpackLog(event, "NodeBalanceUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // ChannelHubTransferFailedIterator is returned from FilterTransferFailed and is used to iterate over the raw logs and unpacked data for TransferFailed events raised by the ChannelHub contract. type ChannelHubTransferFailedIterator struct { Event *ChannelHubTransferFailed // Event containing the contract specifics and raw log @@ -4888,21 +5168,16 @@ func (it *ChannelHubValidatorRegisteredIterator) Close() error { // ChannelHubValidatorRegistered represents a ValidatorRegistered event raised by the ChannelHub contract. type ChannelHubValidatorRegistered struct { - Node common.Address ValidatorId uint8 Validator common.Address Raw types.Log // Blockchain specific contextual infos } -// FilterValidatorRegistered is a free log retrieval operation binding the contract event 0x2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad. +// FilterValidatorRegistered is a free log retrieval operation binding the contract event 0x9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e05. // -// Solidity: event ValidatorRegistered(address indexed node, uint8 indexed validatorId, address indexed validator) -func (_ChannelHub *ChannelHubFilterer) FilterValidatorRegistered(opts *bind.FilterOpts, node []common.Address, validatorId []uint8, validator []common.Address) (*ChannelHubValidatorRegisteredIterator, error) { +// Solidity: event ValidatorRegistered(uint8 indexed validatorId, address indexed validator) +func (_ChannelHub *ChannelHubFilterer) FilterValidatorRegistered(opts *bind.FilterOpts, validatorId []uint8, validator []common.Address) (*ChannelHubValidatorRegisteredIterator, error) { - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } var validatorIdRule []interface{} for _, validatorIdItem := range validatorId { validatorIdRule = append(validatorIdRule, validatorIdItem) @@ -4912,22 +5187,18 @@ func (_ChannelHub *ChannelHubFilterer) FilterValidatorRegistered(opts *bind.Filt validatorRule = append(validatorRule, validatorItem) } - logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "ValidatorRegistered", nodeRule, validatorIdRule, validatorRule) + logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "ValidatorRegistered", validatorIdRule, validatorRule) if err != nil { return nil, err } return &ChannelHubValidatorRegisteredIterator{contract: _ChannelHub.contract, event: "ValidatorRegistered", logs: logs, sub: sub}, nil } -// WatchValidatorRegistered is a free log subscription operation binding the contract event 0x2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad. +// WatchValidatorRegistered is a free log subscription operation binding the contract event 0x9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e05. // -// Solidity: event ValidatorRegistered(address indexed node, uint8 indexed validatorId, address indexed validator) -func (_ChannelHub *ChannelHubFilterer) WatchValidatorRegistered(opts *bind.WatchOpts, sink chan<- *ChannelHubValidatorRegistered, node []common.Address, validatorId []uint8, validator []common.Address) (event.Subscription, error) { +// Solidity: event ValidatorRegistered(uint8 indexed validatorId, address indexed validator) +func (_ChannelHub *ChannelHubFilterer) WatchValidatorRegistered(opts *bind.WatchOpts, sink chan<- *ChannelHubValidatorRegistered, validatorId []uint8, validator []common.Address) (event.Subscription, error) { - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } var validatorIdRule []interface{} for _, validatorIdItem := range validatorId { validatorIdRule = append(validatorIdRule, validatorIdItem) @@ -4937,7 +5208,7 @@ func (_ChannelHub *ChannelHubFilterer) WatchValidatorRegistered(opts *bind.Watch validatorRule = append(validatorRule, validatorItem) } - logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "ValidatorRegistered", nodeRule, validatorIdRule, validatorRule) + logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "ValidatorRegistered", validatorIdRule, validatorRule) if err != nil { return nil, err } @@ -4969,9 +5240,9 @@ func (_ChannelHub *ChannelHubFilterer) WatchValidatorRegistered(opts *bind.Watch }), nil } -// ParseValidatorRegistered is a log parse operation binding the contract event 0x2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad. +// ParseValidatorRegistered is a log parse operation binding the contract event 0x9ee792368f12db92ad66335fa19df35feaec025c86445fea202ab5412a180e05. // -// Solidity: event ValidatorRegistered(address indexed node, uint8 indexed validatorId, address indexed validator) +// Solidity: event ValidatorRegistered(uint8 indexed validatorId, address indexed validator) func (_ChannelHub *ChannelHubFilterer) ParseValidatorRegistered(log types.Log) (*ChannelHubValidatorRegistered, error) { event := new(ChannelHubValidatorRegistered) if err := _ChannelHub.contract.UnpackLog(event, "ValidatorRegistered", log); err != nil { @@ -5050,48 +5321,39 @@ func (it *ChannelHubWithdrawnIterator) Close() error { // ChannelHubWithdrawn represents a Withdrawn event raised by the ChannelHub contract. type ChannelHubWithdrawn struct { - Wallet common.Address Token common.Address Amount *big.Int Raw types.Log // Blockchain specific contextual infos } -// FilterWithdrawn is a free log retrieval operation binding the contract event 0xd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb. +// FilterWithdrawn is a free log retrieval operation binding the contract event 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5. // -// Solidity: event Withdrawn(address indexed wallet, address indexed token, uint256 amount) -func (_ChannelHub *ChannelHubFilterer) FilterWithdrawn(opts *bind.FilterOpts, wallet []common.Address, token []common.Address) (*ChannelHubWithdrawnIterator, error) { +// Solidity: event Withdrawn(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) FilterWithdrawn(opts *bind.FilterOpts, token []common.Address) (*ChannelHubWithdrawnIterator, error) { - var walletRule []interface{} - for _, walletItem := range wallet { - walletRule = append(walletRule, walletItem) - } var tokenRule []interface{} for _, tokenItem := range token { tokenRule = append(tokenRule, tokenItem) } - logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "Withdrawn", walletRule, tokenRule) + logs, sub, err := _ChannelHub.contract.FilterLogs(opts, "Withdrawn", tokenRule) if err != nil { return nil, err } return &ChannelHubWithdrawnIterator{contract: _ChannelHub.contract, event: "Withdrawn", logs: logs, sub: sub}, nil } -// WatchWithdrawn is a free log subscription operation binding the contract event 0xd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb. +// WatchWithdrawn is a free log subscription operation binding the contract event 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5. // -// Solidity: event Withdrawn(address indexed wallet, address indexed token, uint256 amount) -func (_ChannelHub *ChannelHubFilterer) WatchWithdrawn(opts *bind.WatchOpts, sink chan<- *ChannelHubWithdrawn, wallet []common.Address, token []common.Address) (event.Subscription, error) { +// Solidity: event Withdrawn(address indexed token, uint256 amount) +func (_ChannelHub *ChannelHubFilterer) WatchWithdrawn(opts *bind.WatchOpts, sink chan<- *ChannelHubWithdrawn, token []common.Address) (event.Subscription, error) { - var walletRule []interface{} - for _, walletItem := range wallet { - walletRule = append(walletRule, walletItem) - } var tokenRule []interface{} for _, tokenItem := range token { tokenRule = append(tokenRule, tokenItem) } - logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "Withdrawn", walletRule, tokenRule) + logs, sub, err := _ChannelHub.contract.WatchLogs(opts, "Withdrawn", tokenRule) if err != nil { return nil, err } @@ -5123,9 +5385,9 @@ func (_ChannelHub *ChannelHubFilterer) WatchWithdrawn(opts *bind.WatchOpts, sink }), nil } -// ParseWithdrawn is a log parse operation binding the contract event 0xd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb. +// ParseWithdrawn is a log parse operation binding the contract event 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5. // -// Solidity: event Withdrawn(address indexed wallet, address indexed token, uint256 amount) +// Solidity: event Withdrawn(address indexed token, uint256 amount) func (_ChannelHub *ChannelHubFilterer) ParseWithdrawn(log types.Log) (*ChannelHubWithdrawn, error) { event := new(ChannelHubWithdrawn) if err := _ChannelHub.contract.UnpackLog(event, "Withdrawn", log); err != nil { diff --git a/pkg/blockchain/evm/channel_hub_reactor.go b/pkg/blockchain/evm/channel_hub_reactor.go index 5bd6db8c4..e25296e25 100644 --- a/pkg/blockchain/evm/channel_hub_reactor.go +++ b/pkg/blockchain/evm/channel_hub_reactor.go @@ -9,15 +9,117 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "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" ) +// ChannelHubReactorStoreTxHandler 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 ChannelHubReactorStoreTxHandler func(ChannelHubReactorStore) error + +// ChannelHubReactorStoreTxProvider wraps Store operations in a database transaction. +// It accepts a ChannelHubReactorStoreTxHandler and manages transaction lifecycle (begin, commit, rollback). +// Returns an error if the handler fails or the transaction cannot be committed. +type ChannelHubReactorStoreTxProvider func(ChannelHubReactorStoreTxHandler) error + +// ChannelHubReactorStore defines the persistence layer interface for channel and state data. +// All methods should be implemented to work within database transactions. +// Implementations are typically provided by the database layer and wrapped by ChannelHubReactorStoreTxProvider. +type ChannelHubReactorStore interface { + // GetLastStateByChannelID retrieves the most recent state for a given channel. + // If signed is true, only returns states with both user and node signatures. + // Returns nil if no matching state exists. + GetLastStateByChannelID(channelID string, signed bool) (*core.State, error) + + // GetLastUserState retrieves the most recent state for a user's asset across all + // channels and detached chain entries. Returns nil if no matching state exists. + GetLastUserState(wallet, asset string, signed bool) (*core.State, error) + + // GetStateByChannelIDAndVersion retrieves a specific state version for a channel. + // Returns nil if the state with the specified version does not exist. + GetStateByChannelIDAndVersion(channelID string, version uint64) (*core.State, error) + + // UpdateChannel persists changes to a channel's metadata (status, version, etc). + // The channel must already exist in the database. + UpdateChannel(channel core.Channel) error + + // GetChannelByID retrieves a channel by its unique identifier. + // Returns nil if the channel does not exist. + GetChannelByID(channelID string) (*core.Channel, error) + + // ScheduleCheckpoint schedules a checkpoint operation for a home channel state. + // This queues the state to be submitted on-chain to update the channel's on-chain state. + ScheduleCheckpoint(stateID string, chainID uint64) error + + // ScheduleChallenge schedules a challengeChannel(...) submission on the channel's home + // blockchain using the provided state and a node-produced challenger signature. + ScheduleChallenge(stateID string, chainID uint64) error + + // ScheduleInitiateEscrowDeposit schedules an initiate for an escrow deposit operation. + // This queues the state to be submitted on-chain to finalize an escrow deposit. + ScheduleInitiateEscrowDeposit(stateID string, chainID uint64) error + + // ScheduleFinalizeEscrowDeposit schedules a finalize for an escrow deposit operation. + // This queues the state to be submitted on-chain to finalize an escrow deposit. + ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error + + // 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 + + // SetNodeBalance upserts the on-chain liquidity for a given blockchain and asset. + SetNodeBalance(blockchainID uint64, asset string, value decimal.Decimal) error + + // RefreshUserEnforcedBalance recomputes the locked balance from the user's open home channel on-chain state. + RefreshUserEnforcedBalance(wallet, asset string) error + + // LockUserState acquires SELECT ... FOR UPDATE on the user's balance row so the + // caller's transaction serializes against concurrent RPC paths that already lock + // the same row before issuing receiver states. Postgres-only; SQLite is a no-op + // in tests. + LockUserState(wallet, asset string) (decimal.Decimal, error) + + // UpdateStateSigsIfMissing backfills the user and/or node signatures for a stored + // state when the corresponding column is currently NULL. Either signature may be + // empty to skip that side. + UpdateStateSigsIfMissing(channelID string, version uint64, userSig, nodeSig string) error + + // HasSignedFinalize reports whether a node-signed Finalize state exists for the given + // home channel. + HasSignedFinalize(channelID string) (bool, error) + + + // SumNetTransitionAmountAfterVersion returns the net effect on the user's + // home-channel balance of transitions stored against channelID strictly above + // minVersion. Receiver credits (TransferReceive, Release) contribute positively; + // sender debits (TransferSend, Commit) contribute negatively. + SumNetTransitionAmountAfterVersion(channelID string, minVersion uint64) (decimal.Decimal, error) + + // StoreUserState persists a user state row. Used to record a ChallengeRescue squash + // state derived from a closed challenged channel. + StoreUserState(state core.State, applicationID string) error + + // RecordTransaction creates a transaction row linking state transitions. + RecordTransaction(tx core.Transaction, applicationID string) error + + // StoreContractEvent persists a blockchain event to the database. + StoreContractEvent(ev core.BlockchainEvent) error +} + var channelHubAbi *abi.ABI var channelHubFilterer *ChannelHubFilterer var channelHubEventMapping map[common.Hash]string +// encodeSig hex-encodes a signature byte slice or returns an empty string when absent. +func encodeSig(b []byte) string { + if len(b) == 0 { + return "" + } + return hexutil.Encode(b) +} + func initChannelHub() { var err error channelHubAbi, err = ChannelHubMetaData.GetAbi() @@ -36,17 +138,21 @@ func initChannelHub() { } type ChannelHubReactor struct { - blockchainID uint64 - eventHandler core.ChannelHubEventHandler - storeContractEvent StoreContractEvent - onEventProcessed func(blockchainID uint64, success bool) + blockchainID uint64 + nodeAddress string + eventHandler core.ChannelHubEventHandler + assetStore AssetStore + useStoreInTx ChannelHubReactorStoreTxProvider + onEventProcessed func(blockchainID uint64, success bool) } -func NewChannelHubReactor(blockchainID uint64, eventHandler core.ChannelHubEventHandler, storeContractEvent StoreContractEvent) *ChannelHubReactor { +func NewChannelHubReactor(blockchainID uint64, nodeAddress string, eventHandler core.ChannelHubEventHandler, assetStore AssetStore, useStoreInTx ChannelHubReactorStoreTxProvider) *ChannelHubReactor { return &ChannelHubReactor{ - blockchainID: blockchainID, - eventHandler: eventHandler, - storeContractEvent: storeContractEvent, + blockchainID: blockchainID, + nodeAddress: nodeAddress, + eventHandler: eventHandler, + assetStore: assetStore, + useStoreInTx: useStoreInTx, } } @@ -66,96 +172,135 @@ func (r *ChannelHubReactor) HandleEvent(ctx context.Context, l types.Log) error } logger.Debug("received event", "name", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) - var err error - switch eventID { - case channelHubAbi.Events["ChannelCreated"].ID: - err = r.handleHomeChannelCreated(ctx, l) - case channelHubAbi.Events["ChannelCheckpointed"].ID: - err = r.handleHomeChannelCheckpointed(ctx, l) - case channelHubAbi.Events["ChannelDeposited"].ID: - err = r.handleChannelDeposited(ctx, l) - case channelHubAbi.Events["ChannelWithdrawn"].ID: - err = r.handleChannelWithdrawn(ctx, l) - case channelHubAbi.Events["ChannelChallenged"].ID: - err = r.handleHomeChannelChallenged(ctx, l) - case channelHubAbi.Events["ChannelClosed"].ID: - err = r.handleHomeChannelClosed(ctx, l) - case channelHubAbi.Events["EscrowDepositInitiated"].ID: - err = r.handleEscrowDepositInitiated(ctx, l) - case channelHubAbi.Events["EscrowDepositChallenged"].ID: - err = r.handleEscrowDepositChallenged(ctx, l) - case channelHubAbi.Events["EscrowDepositFinalized"].ID: - err = r.handleEscrowDepositFinalized(ctx, l) - case channelHubAbi.Events["EscrowWithdrawalInitiated"].ID: - err = r.handleEscrowWithdrawalInitiated(ctx, l) - case channelHubAbi.Events["EscrowWithdrawalChallenged"].ID: - err = r.handleEscrowWithdrawalChallenged(ctx, l) - case channelHubAbi.Events["EscrowWithdrawalFinalized"].ID: - err = r.handleEscrowWithdrawalFinalized(ctx, l) - case channelHubAbi.Events["EscrowDepositInitiatedOnHome"].ID: - err = r.handleEscrowDepositInitiatedOnHome(ctx, l) - case channelHubAbi.Events["EscrowDepositFinalizedOnHome"].ID: - err = r.handleEscrowDepositFinalizedOnHome(ctx, l) - case channelHubAbi.Events["EscrowWithdrawalInitiatedOnHome"].ID: - err = r.handleEscrowWithdrawalInitiatedOnHome(ctx, l) - case channelHubAbi.Events["EscrowWithdrawalFinalizedOnHome"].ID: - err = r.handleEscrowWithdrawalFinalizedOnHome(ctx, l) - // NOTE: Unimplemented handlers: - case channelHubAbi.Events["MigrationInInitiated"].ID: - err = r.handleHomeChannelMigrated(ctx, l) - case channelHubAbi.Events["MigrationInFinalized"].ID: - err = r.handleMigrationInFinalized(ctx, l) - case channelHubAbi.Events["MigrationOutInitiated"].ID: - err = r.handleMigrationOutInitiated(ctx, l) - case channelHubAbi.Events["MigrationOutFinalized"].ID: - err = r.handleMigrationOutFinalized(ctx, l) - case channelHubAbi.Events["Deposited"].ID: - err = r.handleDeposited(ctx, l) - case channelHubAbi.Events["Withdrawn"].ID: - err = r.handleWithdrawn(ctx, l) - case channelHubAbi.Events["EscrowDepositsPurged"].ID: - err = r.handleEscrowDepositsPurged(ctx, l) - default: - logger.Warn("unknown event: " + eventID.Hex()) - } + err := r.useStoreInTx(func(store ChannelHubReactorStore) error { + var err error + switch eventID { + case channelHubAbi.Events["NodeBalanceUpdated"].ID: + err = r.handleNodeBalanceUpdated(ctx, store, l) + case channelHubAbi.Events["ChannelCreated"].ID: + err = r.handleHomeChannelCreated(ctx, store, l) + case channelHubAbi.Events["ChannelCheckpointed"].ID: + err = r.handleHomeChannelCheckpointed(ctx, store, l) + case channelHubAbi.Events["ChannelDeposited"].ID: + err = r.handleChannelDeposited(ctx, store, l) + case channelHubAbi.Events["ChannelWithdrawn"].ID: + err = r.handleChannelWithdrawn(ctx, store, l) + case channelHubAbi.Events["ChannelChallenged"].ID: + err = r.handleHomeChannelChallenged(ctx, store, l) + case channelHubAbi.Events["ChannelClosed"].ID: + err = r.handleHomeChannelClosed(ctx, store, l) + case channelHubAbi.Events["EscrowDepositInitiated"].ID: + err = r.handleEscrowDepositInitiated(ctx, store, l) + case channelHubAbi.Events["EscrowDepositChallenged"].ID: + err = r.handleEscrowDepositChallenged(ctx, store, l) + case channelHubAbi.Events["EscrowDepositFinalized"].ID: + err = r.handleEscrowDepositFinalized(ctx, store, l) + case channelHubAbi.Events["EscrowWithdrawalInitiated"].ID: + err = r.handleEscrowWithdrawalInitiated(ctx, store, l) + case channelHubAbi.Events["EscrowWithdrawalChallenged"].ID: + err = r.handleEscrowWithdrawalChallenged(ctx, store, l) + case channelHubAbi.Events["EscrowWithdrawalFinalized"].ID: + err = r.handleEscrowWithdrawalFinalized(ctx, store, l) + case channelHubAbi.Events["EscrowDepositInitiatedOnHome"].ID: + err = r.handleEscrowDepositInitiatedOnHome(ctx, store, l) + case channelHubAbi.Events["EscrowDepositFinalizedOnHome"].ID: + err = r.handleEscrowDepositFinalizedOnHome(ctx, store, l) + case channelHubAbi.Events["EscrowWithdrawalInitiatedOnHome"].ID: + err = r.handleEscrowWithdrawalInitiatedOnHome(ctx, store, l) + case channelHubAbi.Events["EscrowWithdrawalFinalizedOnHome"].ID: + err = r.handleEscrowWithdrawalFinalizedOnHome(ctx, store, l) + // NOTE: Unimplemented handlers: + case channelHubAbi.Events["MigrationInInitiated"].ID: + err = r.handleHomeChannelMigrated(ctx, store, l) + case channelHubAbi.Events["MigrationInFinalized"].ID: + err = r.handleMigrationInFinalized(ctx, store, l) + case channelHubAbi.Events["MigrationOutInitiated"].ID: + err = r.handleMigrationOutInitiated(ctx, store, l) + case channelHubAbi.Events["MigrationOutFinalized"].ID: + err = r.handleMigrationOutFinalized(ctx, store, l) + case channelHubAbi.Events["Deposited"].ID: + err = r.handleDeposited(ctx, store, l) + case channelHubAbi.Events["Withdrawn"].ID: + err = r.handleWithdrawn(ctx, store, l) + case channelHubAbi.Events["EscrowDepositsPurged"].ID: + err = r.handleEscrowDepositsPurged(ctx, store, l) + default: + logger.Warn("unknown event: " + eventID.Hex()) + } + if err != nil { + logger.Warn("error processing event", "error", err) + return errors.Wrap(err, "error processing event") + } + + if err := store.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 + }) if r.onEventProcessed != nil { r.onEventProcessed(r.blockchainID, err == nil) } + return err +} + +func (r *ChannelHubReactor) handleNodeBalanceUpdated(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { + event, err := channelHubFilterer.ParseNodeBalanceUpdated(l) if err != nil { - logger.Warn("error processing event", "error", err) - return errors.Wrap(err, "error processing event") + return errors.Wrap(err, "failed to parse NodeBalanceUpdated 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") + asset, err := r.assetStore.GetTokenAsset(r.blockchainID, event.Token.String()) + if err != nil { + return errors.Wrap(err, "failed to get token asset") } - logger.Info("processed event", "event", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) - return nil + decimals, err := r.assetStore.GetTokenDecimals(r.blockchainID, event.Token.String()) + if err != nil { + return errors.Wrap(err, "failed to get token decimals") + } + + balance := decimal.NewFromBigInt(event.Amount, -int32(decimals)) + + ev := core.NodeBalanceUpdatedEvent{ + BlockchainID: r.blockchainID, + Asset: asset, + Balance: balance, + } + return r.eventHandler.HandleNodeBalanceUpdated(ctx, store, &ev) } -func (r *ChannelHubReactor) handleHomeChannelCreated(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleHomeChannelCreated(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { + logger := log.FromContext(ctx) + event, err := channelHubFilterer.ParseChannelCreated(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelCreated event") } + if common.HexToAddress(r.nodeAddress) != event.Definition.Node { + logger.Warn("ignoring ChannelCreated for different node", "eventNode", event.Definition.Node.Hex(), "ourNode", r.nodeAddress) + return nil + } + ev := core.HomeChannelCreatedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.InitialState.Version, + UserSig: encodeSig(event.InitialState.UserSig), } - return r.eventHandler.HandleHomeChannelCreated(ctx, &ev) + return r.eventHandler.HandleHomeChannelCreated(ctx, store, &ev) } -func (r *ChannelHubReactor) handleHomeChannelMigrated(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleHomeChannelMigrated(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseMigrationInInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationInInitiated event") @@ -164,11 +309,12 @@ func (r *ChannelHubReactor) handleHomeChannelMigrated(ctx context.Context, l typ ev := core.HomeChannelMigratedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleHomeChannelMigrated(ctx, &ev) + return r.eventHandler.HandleHomeChannelMigrated(ctx, store, &ev) } -func (r *ChannelHubReactor) handleHomeChannelCheckpointed(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleHomeChannelCheckpointed(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseChannelCheckpointed(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelCheckpointed event") @@ -177,11 +323,12 @@ func (r *ChannelHubReactor) handleHomeChannelCheckpointed(ctx context.Context, l ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.Candidate.Version, + UserSig: encodeSig(event.Candidate.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleChannelDeposited(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleChannelDeposited(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseChannelDeposited(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelDeposited event") @@ -190,11 +337,12 @@ func (r *ChannelHubReactor) handleChannelDeposited(ctx context.Context, l types. ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.Candidate.Version, + UserSig: encodeSig(event.Candidate.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleChannelWithdrawn(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleChannelWithdrawn(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseChannelWithdrawn(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelWithdrawn event") @@ -203,11 +351,12 @@ func (r *ChannelHubReactor) handleChannelWithdrawn(ctx context.Context, l types. ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.Candidate.Version, + UserSig: encodeSig(event.Candidate.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleHomeChannelChallenged(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleHomeChannelChallenged(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseChannelChallenged(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelChallenged event") @@ -217,11 +366,12 @@ func (r *ChannelHubReactor) handleHomeChannelChallenged(ctx context.Context, l t ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.Candidate.Version, ChallengeExpiry: event.ChallengeExpireAt, + UserSig: encodeSig(event.Candidate.UserSig), } - return r.eventHandler.HandleHomeChannelChallenged(ctx, &ev) + return r.eventHandler.HandleHomeChannelChallenged(ctx, store, &ev) } -func (r *ChannelHubReactor) handleHomeChannelClosed(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleHomeChannelClosed(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseChannelClosed(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelClosed event") @@ -230,11 +380,12 @@ func (r *ChannelHubReactor) handleHomeChannelClosed(ctx context.Context, l types ev := core.HomeChannelClosedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.FinalState.Version, + UserSig: encodeSig(event.FinalState.UserSig), } - return r.eventHandler.HandleHomeChannelClosed(ctx, &ev) + return r.eventHandler.HandleHomeChannelClosed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowDepositInitiated(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowDepositInitiated(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowDepositInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositInitiated event") @@ -243,11 +394,12 @@ func (r *ChannelHubReactor) handleEscrowDepositInitiated(ctx context.Context, l ev := core.EscrowDepositInitiatedEvent{ ChannelID: hexutil.Encode(event.EscrowId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleEscrowDepositInitiated(ctx, &ev) + return r.eventHandler.HandleEscrowDepositInitiated(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowDepositChallenged(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowDepositChallenged(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowDepositChallenged(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositChallenged event") @@ -257,11 +409,12 @@ func (r *ChannelHubReactor) handleEscrowDepositChallenged(ctx context.Context, l ChannelID: hexutil.Encode(event.EscrowId[:]), StateVersion: event.State.Version, ChallengeExpiry: event.ChallengeExpireAt, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleEscrowDepositChallenged(ctx, &ev) + return r.eventHandler.HandleEscrowDepositChallenged(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowDepositFinalized(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowDepositFinalized(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowDepositFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositFinalized event") @@ -270,11 +423,12 @@ func (r *ChannelHubReactor) handleEscrowDepositFinalized(ctx context.Context, l ev := core.EscrowDepositFinalizedEvent{ ChannelID: hexutil.Encode(event.EscrowId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleEscrowDepositFinalized(ctx, &ev) + return r.eventHandler.HandleEscrowDepositFinalized(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowWithdrawalInitiated(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowWithdrawalInitiated(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowWithdrawalInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalInitiated event") @@ -283,11 +437,12 @@ func (r *ChannelHubReactor) handleEscrowWithdrawalInitiated(ctx context.Context, ev := core.EscrowWithdrawalInitiatedEvent{ ChannelID: hexutil.Encode(event.EscrowId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleEscrowWithdrawalInitiated(ctx, &ev) + return r.eventHandler.HandleEscrowWithdrawalInitiated(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowWithdrawalChallenged(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowWithdrawalChallenged(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowWithdrawalChallenged(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalChallenged event") @@ -297,11 +452,12 @@ func (r *ChannelHubReactor) handleEscrowWithdrawalChallenged(ctx context.Context ChannelID: hexutil.Encode(event.EscrowId[:]), StateVersion: event.State.Version, ChallengeExpiry: event.ChallengeExpireAt, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleEscrowWithdrawalChallenged(ctx, &ev) + return r.eventHandler.HandleEscrowWithdrawalChallenged(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowWithdrawalFinalized(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowWithdrawalFinalized(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowWithdrawalFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalFinalized event") @@ -310,11 +466,12 @@ func (r *ChannelHubReactor) handleEscrowWithdrawalFinalized(ctx context.Context, ev := core.EscrowWithdrawalFinalizedEvent{ ChannelID: hexutil.Encode(event.EscrowId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleEscrowWithdrawalFinalized(ctx, &ev) + return r.eventHandler.HandleEscrowWithdrawalFinalized(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowDepositInitiatedOnHome(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowDepositInitiatedOnHome(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowDepositInitiatedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositInitiatedOnHome event") @@ -323,11 +480,12 @@ func (r *ChannelHubReactor) handleEscrowDepositInitiatedOnHome(ctx context.Conte ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowDepositFinalizedOnHome(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowDepositFinalizedOnHome(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowDepositFinalizedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositFinalizedOnHome event") @@ -336,11 +494,12 @@ func (r *ChannelHubReactor) handleEscrowDepositFinalizedOnHome(ctx context.Conte ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowWithdrawalInitiatedOnHome(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowWithdrawalInitiatedOnHome(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowWithdrawalInitiatedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalInitiatedOnHome event") @@ -349,11 +508,12 @@ func (r *ChannelHubReactor) handleEscrowWithdrawalInitiatedOnHome(ctx context.Co ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } -func (r *ChannelHubReactor) handleEscrowWithdrawalFinalizedOnHome(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowWithdrawalFinalizedOnHome(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowWithdrawalFinalizedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalFinalizedOnHome event") @@ -362,13 +522,14 @@ func (r *ChannelHubReactor) handleEscrowWithdrawalFinalizedOnHome(ctx context.Co ev := core.HomeChannelCheckpointedEvent{ ChannelID: hexutil.Encode(event.ChannelId[:]), StateVersion: event.State.Version, + UserSig: encodeSig(event.State.UserSig), } - return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) + return r.eventHandler.HandleHomeChannelCheckpointed(ctx, store, &ev) } // Additional event handlers for events not yet defined in core.BlockchainEventHandler -func (r *ChannelHubReactor) handleMigrationInFinalized(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleMigrationInFinalized(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseMigrationInFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationInFinalized event") @@ -381,7 +542,7 @@ func (r *ChannelHubReactor) handleMigrationInFinalized(ctx context.Context, l ty return nil } -func (r *ChannelHubReactor) handleMigrationOutInitiated(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleMigrationOutInitiated(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseMigrationOutInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationOutInitiated event") @@ -394,7 +555,7 @@ func (r *ChannelHubReactor) handleMigrationOutInitiated(ctx context.Context, l t return nil } -func (r *ChannelHubReactor) handleMigrationOutFinalized(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleMigrationOutFinalized(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseMigrationOutFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationOutFinalized event") @@ -407,41 +568,43 @@ func (r *ChannelHubReactor) handleMigrationOutFinalized(ctx context.Context, l t return nil } -func (r *ChannelHubReactor) handleDeposited(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleDeposited(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseDeposited(l) if err != nil { return errors.Wrap(err, "failed to parse Deposited event") } logger := log.FromContext(ctx) logger.Info("Deposited event", - "wallet", event.Wallet.Hex(), "token", event.Token.Hex(), "amount", event.Amount.String()) return nil } -func (r *ChannelHubReactor) handleWithdrawn(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleWithdrawn(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseWithdrawn(l) if err != nil { return errors.Wrap(err, "failed to parse Withdrawn event") } logger := log.FromContext(ctx) logger.Info("Withdrawn event", - "wallet", event.Wallet.Hex(), "token", event.Token.Hex(), "amount", event.Amount.String()) return nil } -func (r *ChannelHubReactor) handleEscrowDepositsPurged(ctx context.Context, l types.Log) error { +func (r *ChannelHubReactor) handleEscrowDepositsPurged(ctx context.Context, store ChannelHubReactorStore, l types.Log) error { event, err := channelHubFilterer.ParseEscrowDepositsPurged(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositsPurged event") } - logger := log.FromContext(ctx) - logger.Info("EscrowDepositsPurged event", "purgedCount", event.PurgedCount.String()) - return nil + escrowIDs := make([]string, len(event.EscrowIds)) + for i, id := range event.EscrowIds { + escrowIDs[i] = hexutil.Encode(id[:]) + } + + ev := core.EscrowDepositsPurgedEvent{EscrowIDs: escrowIDs} + return r.eventHandler.HandleEscrowDepositsPurged(ctx, store, &ev) } diff --git a/pkg/blockchain/evm/channel_hub_reactor_test.go b/pkg/blockchain/evm/channel_hub_reactor_test.go new file mode 100644 index 000000000..818ade62e --- /dev/null +++ b/pkg/blockchain/evm/channel_hub_reactor_test.go @@ -0,0 +1,1115 @@ +package evm + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/core" +) + +// mockChannelHubStore implements ChannelHubReactorStore for testing. +type mockChannelHubStore struct { + mock.Mock +} + +func (m *mockChannelHubStore) GetLastStateByChannelID(channelID string, signed bool) (*core.State, error) { + args := m.Called(channelID, signed) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.State), args.Error(1) +} + +func (m *mockChannelHubStore) GetLastUserState(wallet, asset string, signed bool) (*core.State, error) { + args := m.Called(wallet, asset, signed) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.State), args.Error(1) +} + +func (m *mockChannelHubStore) GetStateByChannelIDAndVersion(channelID string, version uint64) (*core.State, error) { + args := m.Called(channelID, version) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.State), args.Error(1) +} + +func (m *mockChannelHubStore) UpdateChannel(channel core.Channel) error { + args := m.Called(channel) + return args.Error(0) +} + +func (m *mockChannelHubStore) GetChannelByID(channelID string) (*core.Channel, error) { + args := m.Called(channelID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*core.Channel), args.Error(1) +} + +func (m *mockChannelHubStore) ScheduleCheckpoint(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +func (m *mockChannelHubStore) ScheduleChallenge(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +func (m *mockChannelHubStore) ScheduleInitiateEscrowDeposit(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +func (m *mockChannelHubStore) ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +func (m *mockChannelHubStore) ScheduleFinalizeEscrowWithdrawal(stateID string, chainID uint64) error { + args := m.Called(stateID, chainID) + return args.Error(0) +} + +func (m *mockChannelHubStore) SetNodeBalance(blockchainID uint64, asset string, value decimal.Decimal) error { + args := m.Called(blockchainID, asset, value) + return args.Error(0) +} + +func (m *mockChannelHubStore) RefreshUserEnforcedBalance(wallet, asset string) error { + args := m.Called(wallet, asset) + return args.Error(0) +} + +func (m *mockChannelHubStore) StoreContractEvent(ev core.BlockchainEvent) error { + args := m.Called(ev) + return args.Error(0) +} + +func (m *mockChannelHubStore) UpdateStateSigsIfMissing(channelID string, version uint64, userSig, nodeSig string) error { + args := m.Called(channelID, version, userSig, nodeSig) + return args.Error(0) +} + +func (m *mockChannelHubStore) SumNetTransitionAmountAfterVersion(channelID string, minVersion uint64) (decimal.Decimal, error) { + args := m.Called(channelID, minVersion) + return args.Get(0).(decimal.Decimal), args.Error(1) +} + +func (m *mockChannelHubStore) LockUserState(wallet, asset string) (decimal.Decimal, error) { + args := m.Called(wallet, asset) + return args.Get(0).(decimal.Decimal), args.Error(1) +} + +func (m *mockChannelHubStore) HasSignedFinalize(channelID string) (bool, error) { + args := m.Called(channelID) + return args.Bool(0), args.Error(1) +} + + +func (m *mockChannelHubStore) StoreUserState(state core.State, applicationID string) error { + args := m.Called(state, applicationID) + return args.Error(0) +} + +func (m *mockChannelHubStore) RecordTransaction(tx core.Transaction, applicationID string) error { + args := m.Called(tx, applicationID) + return args.Error(0) +} + +// mockChannelHubEventHandler captures events dispatched by the reactor. +type mockChannelHubEventHandler struct { + mock.Mock +} + +func (m *mockChannelHubEventHandler) HandleNodeBalanceUpdated(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.NodeBalanceUpdatedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleHomeChannelCreated(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.HomeChannelCreatedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleHomeChannelMigrated(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.HomeChannelMigratedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleHomeChannelCheckpointed(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.HomeChannelCheckpointedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleHomeChannelChallenged(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.HomeChannelChallengedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleHomeChannelClosed(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.HomeChannelClosedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowDepositInitiated(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowDepositInitiatedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowDepositChallenged(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowDepositChallengedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowDepositFinalized(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowDepositFinalizedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowDepositsPurged(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowDepositsPurgedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowWithdrawalInitiated(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowWithdrawalInitiatedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowWithdrawalChallenged(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowWithdrawalChallengedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +func (m *mockChannelHubEventHandler) HandleEscrowWithdrawalFinalized(ctx context.Context, tx core.ChannelHubEventHandlerStore, ev *core.EscrowWithdrawalFinalizedEvent) error { + args := m.Called(ctx, tx, ev) + return args.Error(0) +} + +// makeState returns a minimal valid ABI State struct with the given version. +func makeState(version uint64) State { + return State{ + Version: version, + Intent: 0, + Metadata: [32]byte{}, + HomeLedger: Ledger{ + ChainId: 1, + Token: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), + Decimals: 6, + UserAllocation: big.NewInt(1000), + UserNetFlow: big.NewInt(0), + NodeAllocation: big.NewInt(2000), + NodeNetFlow: big.NewInt(0), + }, + NonHomeLedger: Ledger{ + ChainId: 0, + Token: common.Address{}, + Decimals: 0, + UserAllocation: big.NewInt(0), + UserNetFlow: big.NewInt(0), + NodeAllocation: big.NewInt(0), + NodeNetFlow: big.NewInt(0), + }, + UserSig: []byte{}, + NodeSig: []byte{}, + } +} + +// packNonIndexed ABI-encodes the non-indexed parameters for the given event. +func packNonIndexed(t *testing.T, eventName string, args ...interface{}) []byte { + t.Helper() + ev, ok := channelHubAbi.Events[eventName] + require.True(t, ok, "event %s not found in ABI", eventName) + + nonIndexed := ev.Inputs.NonIndexed() + data, err := nonIndexed.Pack(args...) + require.NoError(t, err, "failed to pack non-indexed args for %s", eventName) + return data +} + +// newReactor creates a ChannelHubReactor wired to the provided mocks. +func newReactor(blockchainID uint64, nodeAddress string, handler *mockChannelHubEventHandler, assetStore *MockAssetStore, store *mockChannelHubStore) *ChannelHubReactor { + useStoreInTx := func(fn ChannelHubReactorStoreTxHandler) error { + return fn(store) + } + return NewChannelHubReactor(blockchainID, nodeAddress, handler, assetStore, useStoreInTx) +} + +// expectStoreContractEvent sets up the mock expectation for StoreContractEvent. +func expectStoreContractEvent(store *mockChannelHubStore, eventName string, blockNumber uint64, blockchainID uint64) { + store.On("StoreContractEvent", mock.MatchedBy(func(ev core.BlockchainEvent) bool { + return ev.Name == eventName && ev.BlockNumber == blockNumber && ev.BlockchainID == blockchainID + })).Return(nil) +} + +func TestChannelHubReactor_HandleNodeBalanceUpdated(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := common.HexToAddress("0x1111111111111111111111111111111111111111") + tokenAddr := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") + amount := big.NewInt(1_000_000) // 1 USDC (6 decimals) + + data := packNonIndexed(t, "NodeBalanceUpdated", amount) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["NodeBalanceUpdated"].ID, + common.BytesToHash(tokenAddr.Bytes()), + }, + Data: data, + BlockNumber: 200, + TxHash: common.HexToHash("0xabc123"), + Index: 0, + } + + t.Run("success", func(t *testing.T) { + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + assetStore.On("GetTokenAsset", blockchainID, tokenAddr.String()).Return("usdc", nil) + assetStore.On("GetTokenDecimals", blockchainID, tokenAddr.String()).Return(uint8(6), nil) + + handler.On("HandleNodeBalanceUpdated", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.NodeBalanceUpdatedEvent) bool { + return ev.BlockchainID == blockchainID && + ev.Asset == "usdc" && + ev.Balance.Equal(decimal.NewFromBigInt(amount, -6)) + })).Return(nil) + + expectStoreContractEvent(store, "NodeBalanceUpdated", 200, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr.String(), handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) + }) + + t.Run("handler error", func(t *testing.T) { + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + assetStore.On("GetTokenAsset", blockchainID, tokenAddr.String()).Return("usdc", nil) + assetStore.On("GetTokenDecimals", blockchainID, tokenAddr.String()).Return(uint8(6), nil) + + handler.On("HandleNodeBalanceUpdated", mock.Anything, mock.Anything, mock.Anything).Return(assert.AnError) + + reactor := newReactor(blockchainID, nodeAddr.String(), handler, assetStore, store) + + var processedSuccess bool + reactor.SetOnEventProcessed(func(_ uint64, success bool) { + processedSuccess = success + }) + + err := reactor.HandleEvent(context.Background(), logEntry) + require.Error(t, err) + assert.False(t, processedSuccess) + }) +} + +func TestChannelHubReactor_HandleHomeChannelCreated(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := common.HexToAddress("0x1111111111111111111111111111111111111111") + userAddr := common.HexToAddress("0x2222222222222222222222222222222222222222") + channelID := common.HexToHash("0xcc01") + + state := makeState(1) + def := ChannelDefinition{ + ChallengeDuration: 3600, + User: userAddr, + Node: nodeAddr, + Nonce: 1, + ApprovedSignatureValidators: big.NewInt(0), + Metadata: [32]byte{}, + } + + data := packNonIndexed(t, "ChannelCreated", def, state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelCreated"].ID, + channelID, + common.BytesToHash(userAddr.Bytes()), + }, + Data: data, + BlockNumber: 300, + TxHash: common.HexToHash("0xdef456"), + Index: 1, + } + + t.Run("success", func(t *testing.T) { + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCreated", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCreatedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 1 + })).Return(nil) + + expectStoreContractEvent(store, "ChannelCreated", 300, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr.String(), handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) + }) + + t.Run("ignores other nodes", func(t *testing.T) { + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + expectStoreContractEvent(store, "ChannelCreated", 300, blockchainID) + + otherNode := "0x3333333333333333333333333333333333333333" + reactor := newReactor(blockchainID, otherNode, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertNotCalled(t, "HandleHomeChannelCreated") + store.AssertExpectations(t) + }) +} + +func TestChannelHubReactor_HandleHomeChannelCheckpointed(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc02") + + state := makeState(5) + data := packNonIndexed(t, "ChannelCheckpointed", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelCheckpointed"].ID, + channelID, + }, + Data: data, + BlockNumber: 400, + TxHash: common.HexToHash("0x111"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 5 + })).Return(nil) + + expectStoreContractEvent(store, "ChannelCheckpointed", 400, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +// TestChannelHubReactor_HandleHomeChannelCheckpointed_ForwardsUserSig confirms the reactor +// hex-encodes Candidate.UserSig from the parsed event payload and surfaces it to the handler. +// Without this the wedge-recovery backfill in HandleHomeChannelCheckpointed has nothing to write. +func TestChannelHubReactor_HandleHomeChannelCheckpointed_ForwardsUserSig(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc02ff") + + state := makeState(7) + state.UserSig = []byte{0xde, 0xad, 0xbe, 0xef} + data := packNonIndexed(t, "ChannelCheckpointed", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelCheckpointed"].ID, + channelID, + }, + Data: data, + BlockNumber: 401, + TxHash: common.HexToHash("0x112"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.UserSig == hexutil.Encode([]byte{0xde, 0xad, 0xbe, 0xef}) + })).Return(nil) + + expectStoreContractEvent(store, "ChannelCheckpointed", 401, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleHomeChannelChallenged(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc03") + + state := makeState(4) + challengeExpiry := uint64(9999999) + data := packNonIndexed(t, "ChannelChallenged", state, challengeExpiry) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelChallenged"].ID, + channelID, + }, + Data: data, + BlockNumber: 500, + TxHash: common.HexToHash("0x222"), + Index: 2, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelChallenged", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelChallengedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && + ev.StateVersion == 4 && + ev.ChallengeExpiry == challengeExpiry + })).Return(nil) + + expectStoreContractEvent(store, "ChannelChallenged", 500, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleHomeChannelClosed(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc04") + + state := makeState(10) + data := packNonIndexed(t, "ChannelClosed", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelClosed"].ID, + channelID, + }, + Data: data, + BlockNumber: 600, + TxHash: common.HexToHash("0x333"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelClosed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelClosedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 10 + })).Return(nil) + + expectStoreContractEvent(store, "ChannelClosed", 600, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleChannelDeposited(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc05") + + state := makeState(7) + data := packNonIndexed(t, "ChannelDeposited", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelDeposited"].ID, + channelID, + }, + Data: data, + BlockNumber: 700, + TxHash: common.HexToHash("0x444"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + // ChannelDeposited dispatches HandleHomeChannelCheckpointed + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 7 + })).Return(nil) + + expectStoreContractEvent(store, "ChannelDeposited", 700, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleChannelWithdrawn(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc06") + + state := makeState(8) + data := packNonIndexed(t, "ChannelWithdrawn", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelWithdrawn"].ID, + channelID, + }, + Data: data, + BlockNumber: 800, + TxHash: common.HexToHash("0x555"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + // ChannelWithdrawn dispatches HandleHomeChannelCheckpointed + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 8 + })).Return(nil) + + expectStoreContractEvent(store, "ChannelWithdrawn", 800, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowDepositInitiated(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee01") + channelID := common.HexToHash("0xcc01") + + state := makeState(1) + data := packNonIndexed(t, "EscrowDepositInitiated", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowDepositInitiated"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 900, + TxHash: common.HexToHash("0x666"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowDepositInitiated", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowDepositInitiatedEvent) bool { + return ev.ChannelID == hexutil.Encode(escrowID[:]) && ev.StateVersion == 1 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowDepositInitiated", 900, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowDepositChallenged(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee02") + + state := makeState(3) + challengeExpiry := uint64(8888888) + data := packNonIndexed(t, "EscrowDepositChallenged", state, challengeExpiry) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowDepositChallenged"].ID, + escrowID, + }, + Data: data, + BlockNumber: 1000, + TxHash: common.HexToHash("0x777"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowDepositChallenged", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowDepositChallengedEvent) bool { + return ev.ChannelID == hexutil.Encode(escrowID[:]) && + ev.StateVersion == 3 && + ev.ChallengeExpiry == challengeExpiry + })).Return(nil) + + expectStoreContractEvent(store, "EscrowDepositChallenged", 1000, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowDepositFinalized(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee03") + channelID := common.HexToHash("0xcc01") + + state := makeState(5) + data := packNonIndexed(t, "EscrowDepositFinalized", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowDepositFinalized"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1100, + TxHash: common.HexToHash("0x888"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowDepositFinalized", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowDepositFinalizedEvent) bool { + return ev.ChannelID == hexutil.Encode(escrowID[:]) && ev.StateVersion == 5 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowDepositFinalized", 1100, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowWithdrawalInitiated(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee04") + channelID := common.HexToHash("0xcc01") + + state := makeState(1) + data := packNonIndexed(t, "EscrowWithdrawalInitiated", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowWithdrawalInitiated"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1200, + TxHash: common.HexToHash("0x999"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowWithdrawalInitiated", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowWithdrawalInitiatedEvent) bool { + return ev.ChannelID == hexutil.Encode(escrowID[:]) && ev.StateVersion == 1 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowWithdrawalInitiated", 1200, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowWithdrawalChallenged(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee05") + + state := makeState(3) + challengeExpiry := uint64(7777777) + data := packNonIndexed(t, "EscrowWithdrawalChallenged", state, challengeExpiry) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowWithdrawalChallenged"].ID, + escrowID, + }, + Data: data, + BlockNumber: 1300, + TxHash: common.HexToHash("0xaaa"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowWithdrawalChallenged", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowWithdrawalChallengedEvent) bool { + return ev.ChannelID == hexutil.Encode(escrowID[:]) && + ev.StateVersion == 3 && + ev.ChallengeExpiry == challengeExpiry + })).Return(nil) + + expectStoreContractEvent(store, "EscrowWithdrawalChallenged", 1300, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowWithdrawalFinalized(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee06") + channelID := common.HexToHash("0xcc01") + + state := makeState(5) + data := packNonIndexed(t, "EscrowWithdrawalFinalized", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowWithdrawalFinalized"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1400, + TxHash: common.HexToHash("0xbbb"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowWithdrawalFinalized", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowWithdrawalFinalizedEvent) bool { + return ev.ChannelID == hexutil.Encode(escrowID[:]) && ev.StateVersion == 5 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowWithdrawalFinalized", 1400, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowDepositInitiatedOnHome(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee07") + channelID := common.HexToHash("0xcc01") + + state := makeState(3) + data := packNonIndexed(t, "EscrowDepositInitiatedOnHome", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowDepositInitiatedOnHome"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1500, + TxHash: common.HexToHash("0xccc"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + // Dispatches HandleHomeChannelCheckpointed with the channelId topic + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 3 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowDepositInitiatedOnHome", 1500, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowDepositFinalizedOnHome(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee08") + channelID := common.HexToHash("0xcc01") + + state := makeState(6) + data := packNonIndexed(t, "EscrowDepositFinalizedOnHome", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowDepositFinalizedOnHome"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1600, + TxHash: common.HexToHash("0xddd"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 6 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowDepositFinalizedOnHome", 1600, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowWithdrawalInitiatedOnHome(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee09") + channelID := common.HexToHash("0xcc01") + + state := makeState(4) + data := packNonIndexed(t, "EscrowWithdrawalInitiatedOnHome", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowWithdrawalInitiatedOnHome"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1700, + TxHash: common.HexToHash("0xeee"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 4 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowWithdrawalInitiatedOnHome", 1700, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowWithdrawalFinalizedOnHome(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + escrowID := common.HexToHash("0xee10") + channelID := common.HexToHash("0xcc01") + + state := makeState(9) + data := packNonIndexed(t, "EscrowWithdrawalFinalizedOnHome", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowWithdrawalFinalizedOnHome"].ID, + escrowID, + channelID, + }, + Data: data, + BlockNumber: 1800, + TxHash: common.HexToHash("0xfff"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.HomeChannelCheckpointedEvent) bool { + return ev.ChannelID == hexutil.Encode(channelID[:]) && ev.StateVersion == 9 + })).Return(nil) + + expectStoreContractEvent(store, "EscrowWithdrawalFinalizedOnHome", 1800, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_HandleEscrowDepositsPurged(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + + escrowID1 := common.HexToHash("0xee10") + escrowID2 := common.HexToHash("0xee11") + escrowIds := [][32]byte{escrowID1, escrowID2} + purgedCount := big.NewInt(2) + + data := packNonIndexed(t, "EscrowDepositsPurged", escrowIds, purgedCount) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["EscrowDepositsPurged"].ID, + }, + Data: data, + BlockNumber: 1200, + TxHash: common.HexToHash("0x999"), + Index: 0, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleEscrowDepositsPurged", mock.Anything, mock.Anything, mock.MatchedBy(func(ev *core.EscrowDepositsPurgedEvent) bool { + return len(ev.EscrowIDs) == 2 && + ev.EscrowIDs[0] == hexutil.Encode(escrowID1[:]) && + ev.EscrowIDs[1] == hexutil.Encode(escrowID2[:]) + })).Return(nil) + + expectStoreContractEvent(store, "EscrowDepositsPurged", 1200, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + handler.AssertExpectations(t) + store.AssertExpectations(t) +} + +func TestChannelHubReactor_UnknownEvent(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + + logEntry := types.Log{ + Topics: []common.Hash{ + common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + }, + BlockNumber: 999, + } + + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) +} + +func TestChannelHubReactor_OnEventProcessedCallback(t *testing.T) { + blockchainID := uint64(1) + nodeAddr := "0x1111111111111111111111111111111111111111" + channelID := common.HexToHash("0xcc99") + + state := makeState(1) + data := packNonIndexed(t, "ChannelCheckpointed", state) + + logEntry := types.Log{ + Topics: []common.Hash{ + channelHubAbi.Events["ChannelCheckpointed"].ID, + channelID, + }, + Data: data, + BlockNumber: 50, + TxHash: common.HexToHash("0xcb01"), + Index: 0, + } + + t.Run("callback receives true on success", func(t *testing.T) { + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.Anything).Return(nil) + expectStoreContractEvent(store, "ChannelCheckpointed", 50, blockchainID) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + + var cbBlockchainID uint64 + var cbSuccess bool + reactor.SetOnEventProcessed(func(bid uint64, success bool) { + cbBlockchainID = bid + cbSuccess = success + }) + + err := reactor.HandleEvent(context.Background(), logEntry) + require.NoError(t, err) + assert.Equal(t, blockchainID, cbBlockchainID) + assert.True(t, cbSuccess) + }) + + t.Run("callback receives false on handler error", func(t *testing.T) { + store := new(mockChannelHubStore) + handler := new(mockChannelHubEventHandler) + assetStore := new(MockAssetStore) + + handler.On("HandleHomeChannelCheckpointed", mock.Anything, mock.Anything, mock.Anything).Return(assert.AnError) + + reactor := newReactor(blockchainID, nodeAddr, handler, assetStore, store) + + var cbSuccess bool + reactor.SetOnEventProcessed(func(_ uint64, success bool) { + cbSuccess = success + }) + + err := reactor.HandleEvent(context.Background(), logEntry) + require.Error(t, err) + assert.False(t, cbSuccess) + }) +} diff --git a/pkg/blockchain/evm/client_opts.go b/pkg/blockchain/evm/client_opts.go index fe9368721..15ed44eb4 100644 --- a/pkg/blockchain/evm/client_opts.go +++ b/pkg/blockchain/evm/client_opts.go @@ -23,6 +23,26 @@ func (ch ClientBalanceCheck) apply(c *BlockchainClient) { c.requireCheckBalance = ch.RequireBalanceCheck } +// ClientGasLimit forces a fixed GasLimit on every transaction sent through the +// client, bypassing eth_estimateGas. Set to 0 to keep the default behavior +// (estimate per tx). Useful for chains whose RPC rejects estimateGas — e.g. +// XRPL EVM testnet returns "gas cap cannot be lower than 21000". +// +// TODO: temporary workaround. A single client-wide gas cap overshoots cheap +// calls and may undershoot expensive ones. Replace with per-action gas +// estimation that picks a healthy limit per tx type (deposit, withdraw, +// challenge, etc.), falling back to a sane floor only when the RPC refuses +// estimateGas. +type ClientGasLimit struct { + GasLimit uint64 +} + +func (g ClientGasLimit) apply(c *BlockchainClient) { + if g.GasLimit > 0 { + c.transactOpts.GasLimit = g.GasLimit + } +} + type ClientFeeCheck struct { RequirePositiveNativeBalance bool } diff --git a/pkg/blockchain/evm/interface.go b/pkg/blockchain/evm/interface.go index 9a28ccd97..23e5bf639 100644 --- a/pkg/blockchain/evm/interface.go +++ b/pkg/blockchain/evm/interface.go @@ -6,12 +6,17 @@ import ( 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) error -type StoreContractEvent func(ev core.BlockchainEvent) error -type LatestEventGetter func(contractAddress string, blockchainID uint64) (ev core.BlockchainEvent, err error) + +// ContractEventGetter is used by Listener for resumption and deduplication. +type ContractEventGetter interface { + // GetLatestContractEventBlockNumber returns the block to resume from (0 = start fresh). + GetLatestContractEventBlockNumber(contractAddress string, blockchainID uint64) (lastBlock uint64, err error) + // IsContractEventPresent checks whether a specific event was already processed. + IsContractEventPresent(blockchainID, blockNumber uint64, txHash string, logIndex uint32) (isPresent bool, err error) +} type AssetStore interface { // GetAssetDecimals checks if an asset exists and returns its decimals in YN @@ -22,6 +27,9 @@ type AssetStore interface { // GetTokenAddress returns the token address for a given asset on a specific blockchain GetTokenAddress(asset string, blockchainID uint64) (string, error) + + // GetTokenAsset returns the asset for a token on a specific blockchain + GetTokenAsset(blockchainID uint64, tokenAddress string) (string, error) } type EVMClient interface { diff --git a/pkg/blockchain/evm/listener.go b/pkg/blockchain/evm/listener.go index 1fcf9efc2..1e2a013d7 100644 --- a/pkg/blockchain/evm/listener.go +++ b/pkg/blockchain/evm/listener.go @@ -2,6 +2,7 @@ package evm import ( "context" + "fmt" "math/big" "sync" "sync/atomic" @@ -11,34 +12,31 @@ import ( "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 ( - maxBackOffCount = 5 + maxBackOffCount = 5 + rpcRequestTimeout = 1 * time.Minute ) +// Listener watches a single contract for on-chain events, combining historical +// log reconciliation with a live WebSocket subscription to guarantee gap-free, +// deduplicated delivery even across restarts. Cancel the context passed to Listen +// for graceful shutdown. type Listener struct { contractAddress common.Address client bind.ContractBackend blockchainID uint64 - blockStep uint64 + blockStep uint64 // max blocks per FilterLogs call during reconciliation logger log.Logger handleEvent HandleEvent - getLatestEvent LatestEventGetter + eventGetter ContractEventGetter } -func NewListener(contractAddress common.Address, client bind.ContractBackend, blockchainID uint64, blockStep uint64, logger log.Logger, eventHandler HandleEvent, getLatestEvent LatestEventGetter) *Listener { - if getLatestEvent == nil { - getLatestEvent = func(contractAddress string, networkID uint64) (core.BlockchainEvent, error) { - return core.BlockchainEvent{ - BlockNumber: 0, - LogIndex: 0, - }, nil - } - } +// NewListener creates a Listener. blockStep controls how many blocks are fetched +// per RPC call during historical reconciliation. +func NewListener(contractAddress common.Address, client bind.ContractBackend, blockchainID uint64, blockStep uint64, logger log.Logger, eventHandler HandleEvent, eventGetter ContractEventGetter) *Listener { return &Listener{ contractAddress: contractAddress, client: client, @@ -46,12 +44,12 @@ func NewListener(contractAddress common.Address, client bind.ContractBackend, bl blockStep: blockStep, logger: logger.WithName("evm"), handleEvent: eventHandler, - getLatestEvent: getLatestEvent, + eventGetter: eventGetter, } } -// Listen starts the event listener in a background goroutine. -// The handleClosure callback is invoked when the listener exits, with an error if any. +// Listen starts the listener in a background goroutine. handleClosure is called +// exactly once after the listener stops; err is non-nil only if the handler failed. func (l *Listener) Listen(ctx context.Context, handleClosure func(err error)) { childCtx, cancel := context.WithCancel(ctx) wg := sync.WaitGroup{} @@ -85,105 +83,254 @@ func (l *Listener) Listen(ctx context.Context, handleClosure func(err error)) { }() } -// listenEvents listens for blockchain events and processes them with the provided handler +// logBackOff computes the backoff duration and logs accordingly. +// Returns the duration and true if the caller should proceed, or false if the limit was exceeded (fatal logged). +func (l *Listener) logBackOff(count uint64, originator string) (time.Duration, bool) { + d := backOffDuration(int(count)) + if d < 0 { + l.logger.Fatal("back off limit reached, exiting", "originator", originator, "backOffCollisionCount", count) + return 0, false + } + if d > 0 { + l.logger.Info("backing off", "originator", originator, "backOffCollisionCount", count) + } + return d, true +} + +// listenEvents is the main loop. Each iteration: +// 1. Subscribes to live events (buffered in currentCh). +// 2. Fetches the chain tip — done after subscribing so no events fall through the gap. +// 3. Launches reconcileBlockRange in a goroutine (lastBlock → chain tip → historicalCh). +// 4. Calls processEvents: drains historicalCh first, then switches to currentCh. +// +// On subscription failure it retries with exponential backoff. Returns non-nil only +// when the handler or the event-presence check fails. func (l *Listener) listenEvents(ctx context.Context) error { - ev, err := l.getLatestEvent(l.contractAddress.String(), l.blockchainID) + lastBlock, err := l.eventGetter.GetLatestContractEventBlockNumber(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 fmt.Errorf("failed to get latest processed block: %w", err) } - lastBlock := ev.BlockNumber - lastIndex := ev.LogIndex var backOffCount atomic.Uint64 - var historicalCh, currentCh chan types.Log - var eventSubscription event.Subscription l.logger.Info("starting listening events", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) for { - if eventSubscription == nil { - waitForBackOffTimeout(l.logger, int(backOffCount.Load()), "event subscription") - - historicalCh = make(chan types.Log, 1) - currentCh = make(chan types.Log, 100) + d, ok := l.logBackOff(backOffCount.Load(), "event subscription") + if !ok { + return nil + } + select { + case <-ctx.Done(): + l.logger.Info("stopping event listener", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + return nil + case <-time.After(d): + } + if ctx.Err() != nil { + l.logger.Info("stopping event listener", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + return nil + } - if lastBlock == 0 { - l.logger.Info("skipping historical logs fetching", - "blockchainID", l.blockchainID, - "contractAddress", l.contractAddress.String()) - } else { - var header *types.Header - var err error - headerCtx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - header, err = l.client.HeaderByNumber(headerCtx, nil) - cancel() - if err != nil { - l.logger.Error("failed to get latest block", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) - backOffCount.Add(1) - continue - } + historicalCh := make(chan types.Log, 1) + currentCh := make(chan types.Log, 100) - // TODO: ensure that new events start to be processed only after all historical ones are processed - go l.reconcileBlockRange( - header.Number.Uint64(), - lastBlock, - lastIndex, - historicalCh, - ) - } + // Subscribe to live events first so nothing is missed while reconciling. + watchFQ := ethereum.FilterQuery{ + Addresses: []common.Address{l.contractAddress}, + } + eventSubscription, err := l.client.SubscribeFilterLogs(context.Background(), watchFQ, currentCh) + if err != nil { + l.logger.Error("failed to subscribe on events", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + backOffCount.Add(1) + continue + } - watchFQ := ethereum.FilterQuery{ - Addresses: []common.Address{l.contractAddress}, - } - eventSub, err := l.client.SubscribeFilterLogs(context.Background(), watchFQ, currentCh) + // Fetch current block height after subscribing to avoid a gap. + var cancelReconcile context.CancelFunc + if lastBlock == 0 { + l.logger.Info("skipping historical logs fetching", + "blockchainID", l.blockchainID, + "contractAddress", l.contractAddress.String()) + close(historicalCh) + } else { + headerCtx, headerCancel := context.WithTimeout(context.Background(), rpcRequestTimeout) + header, err := l.client.HeaderByNumber(headerCtx, nil) + headerCancel() if err != nil { - l.logger.Error("failed to subscribe on events", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + l.logger.Error("failed to get latest block", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + eventSubscription.Unsubscribe() backOffCount.Add(1) continue } - eventSubscription = eventSub - l.logger.Info("watching events", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) - backOffCount.Store(0) + var reconcileCtx context.Context + reconcileCtx, cancelReconcile = context.WithCancel(ctx) + currentBlock := header.Number.Uint64() + go func() { + l.reconcileBlockRange(reconcileCtx, currentBlock, lastBlock, historicalCh) + close(historicalCh) + }() } + l.logger.Info("watching events", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + backOffCount.Store(0) + + err = l.processEvents(ctx, eventSubscription, historicalCh, currentCh, &lastBlock) + if cancelReconcile != nil { + cancelReconcile() + } + if err != nil { + return err + } + } +} + +// processEvents runs two sequential phases: historical (historicalCh until closed), +// then live (currentCh until ctx or subscription death). In each phase the first +// events are checked via IsContractEventPresent; once a non-present event is found +// the check is skipped for the rest of that phase (events are strictly ordered). +// Returns nil on subscription loss (reconnect), non-nil on handler/check failure. +// +// Listener ordering & idempotency invariant +// ----------------------------------------- +// Downstream handlers (and any code reasoning about the relative arrival order +// of on-chain events) may rely on the following guarantees provided by this +// loop. Changes that weaken any of them must update every consumer that cites +// this invariant by name. +// +// 1. Strict per-contract ordering. Within a single Listener, events are +// delivered to handleEvent in ascending (block_number, log_index) order +// across the historical → live transition. Phase 1 drains historicalCh to +// completion before phase 2 reads from currentCh, and the upstream +// reconcileBlockRange + live subscription preserve chain order within each +// phase. +// +// 2. Idempotent resume. On restart, IsContractEventPresent gates the first +// event of each phase: events already persisted in a prior run are skipped +// rather than reprocessed. Once a non-present event is seen the check is +// dropped for the remainder of the phase (safe because of guarantee 1). +// +// 3. Cursor advances only on handler success. lastBlock is updated on each +// live event, but a non-nil return from handleEvent unsubscribes and +// surfaces the error to the caller without persisting any state past the +// failed event; the next Listen invocation re-fetches from the same +// cursor. Transient handler failures retry instead of silently dropping. +// +// 4. Reorged-out logs are discarded. Live deliveries with Removed=true are +// dropped. A reorg that fully removes a ChannelChallenged log also +// removes the matching on-chain status transition to DISPUTED, so the +// contract's Path-1 (challenge-timeout) close cannot subsequently fire +// for the same channel. +// +// A consequence used by the nitronode event handlers: for any channel that +// closes via Path-1 (challenge-timeout, ChannelHub Closed-from-DISPUTED), +// HandleHomeChannelChallenged is guaranteed to run before HandleHomeChannelClosed +// for that channel. See nitronode/event_handlers/service.go (audit finding +// MF3-I01) for the wedge case this rules out. +func (l *Listener) processEvents( + ctx context.Context, + eventSubscription interface { + Unsubscribe() + Err() <-chan error + }, + historicalCh <-chan types.Log, + currentCh <-chan types.Log, + lastBlock *uint64, +) error { + // Phase 1: drain all historical events before processing live ones. + historicalCheckDone := false + for historicalCh != nil { select { case <-ctx.Done(): l.logger.Info("stopping event listener", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) 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) - if err := l.handleEvent(ctx, eventLog); err != nil { + case eventLog, ok := <-historicalCh: + if !ok { + historicalCh = nil + break + } + if !historicalCheckDone { + present, err := l.eventGetter.IsContractEventPresent(l.blockchainID, eventLog.BlockNumber, eventLog.TxHash.Hex(), uint32(eventLog.Index)) + if err != nil { + eventSubscription.Unsubscribe() + return fmt.Errorf("failed to check historical event presence: %w", err) + } + if present { + l.logger.Debug("skipping already present historical event", "blockchainID", l.blockchainID, "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) + continue + } + historicalCheckDone = true + } + l.logger.Debug("received historical event", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String(), "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) + evCtx := log.SetContextLogger(context.Background(), l.logger) + if err := l.handleEvent(evCtx, eventLog); err != nil { + eventSubscription.Unsubscribe() 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) + case err := <-eventSubscription.Err(): + if err != nil { + l.logger.Error("event subscription error", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + } else { + l.logger.Debug("subscription closed, resubscribing", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + } + eventSubscription.Unsubscribe() + return nil + } + } - ctx := log.SetContextLogger(context.Background(), l.logger) - if err := l.handleEvent(ctx, eventLog); err != nil { + // Phase 2: process live events from subscription. + currentCheckDone := false + for { + select { + case <-ctx.Done(): + l.logger.Info("stopping event listener", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) + eventSubscription.Unsubscribe() + return nil + case eventLog := <-currentCh: + // During a chain reorganization geth re-delivers orphaned logs with + // Removed: true. Skip them to avoid applying phantom state changes. + if eventLog.Removed { + l.logger.Warn("skipping removed log from reorg", "blockchainID", l.blockchainID, "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index, "txHash", eventLog.TxHash.Hex()) + continue + } + *lastBlock = eventLog.BlockNumber + if !currentCheckDone { + present, err := l.eventGetter.IsContractEventPresent(l.blockchainID, eventLog.BlockNumber, eventLog.TxHash.Hex(), uint32(eventLog.Index)) + if err != nil { + eventSubscription.Unsubscribe() + return fmt.Errorf("failed to check current event presence: %w", err) + } + if present { + l.logger.Debug("skipping already present current event", "blockchainID", l.blockchainID, "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) + continue + } + currentCheckDone = true + } + l.logger.Debug("received current event", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String(), "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) + evCtx := log.SetContextLogger(context.Background(), l.logger) + if err := l.handleEvent(evCtx, eventLog); err != nil { + eventSubscription.Unsubscribe() return err } case err := <-eventSubscription.Err(): if err != nil { l.logger.Error("event subscription error", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) - eventSubscription.Unsubscribe() - // NOTE: do not increment backOffCount here, as connection errors on continuous subscriptions are normal } else { l.logger.Debug("subscription closed, resubscribing", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) } - - eventSubscription = nil + eventSubscription.Unsubscribe() + return nil } } } +// reconcileBlockRange fetches logs from lastBlock to currentBlock in blockStep-sized +// windows, sending each log to historicalCh. Caller closes historicalCh after return. +// Uses a dedicated context so it can be cancelled when the subscription drops. func (l *Listener) reconcileBlockRange( + ctx context.Context, currentBlock uint64, lastBlock uint64, - lastIndex uint32, historicalCh chan types.Log, ) { var backOffCount atomic.Uint64 @@ -191,12 +338,19 @@ func (l *Listener) reconcileBlockRange( endBlock := startBlock + l.blockStep for currentBlock > startBlock { - waitForBackOffTimeout(l.logger, int(backOffCount.Load()), "reconcile block range") + d, ok := l.logBackOff(backOffCount.Load(), "reconcile block range") + if !ok { + return + } + select { + case <-ctx.Done(): + return + case <-time.After(d): + } + if ctx.Err() != nil { + return + } - // We need to refetch events starting from last known block without adding 1 to it - // because it's possible that block includes more than 1 event, and some may be still unprocessed. - // - // This will cause duplicate key error in logs, but it's completely fine. if endBlock > currentBlock { endBlock = currentBlock } @@ -205,14 +359,15 @@ func (l *Listener) reconcileBlockRange( Addresses: []common.Address{l.contractAddress}, FromBlock: new(big.Int).SetUint64(startBlock), ToBlock: new(big.Int).SetUint64(endBlock), - // Topics: topics, } - logsCtx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + logsCtx, cancel := context.WithTimeout(ctx, rpcRequestTimeout) logs, err := l.client.FilterLogs(logsCtx, fetchFQ) cancel() if err != nil { - // TODO: divide previous block range by 2 + if ctx.Err() != nil { + return + } backOffCount.Add(1) l.logger.Error("failed to filter logs", "error", err, @@ -220,7 +375,7 @@ func (l *Listener) reconcileBlockRange( "contractAddress", l.contractAddress.String(), "startBlock", startBlock, "endBlock", endBlock) - continue // retry with the advised block range + continue } l.logger.Info("fetched historical logs", "blockchainID", l.blockchainID, @@ -230,16 +385,23 @@ func (l *Listener) reconcileBlockRange( "endBlock", endBlock) for _, ethLog := range logs { - // Filter out previously known events - if ethLog.BlockNumber == lastBlock && ethLog.Index <= uint(lastIndex) { - l.logger.Info("skipping previously known event", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String(), "blockNumber", ethLog.BlockNumber, "logIndex", ethLog.Index) - continue + select { + case <-ctx.Done(): + return + case historicalCh <- ethLog: } - - historicalCh <- ethLog } startBlock = endBlock + 1 endBlock += l.blockStep } } + +// TODO: the current reorg handling (skipping Removed logs) prevents new damage but +// does not undo side effects from the original delivery if it was already processed. +// A more robust approach is a confirmation buffer: hold live logs in memory keyed by +// block number, only apply them after N confirmations (new blocks on top), and discard +// any log that arrives with Removed: true while still in the buffer. This adds N blocks +// of latency (~12s × N on mainnet) but guarantees that only finalized events reach the +// handler. On L2s where reorgs are near-zero, the latency trade-off may not be worth it, +// so this should be configurable per chain. diff --git a/pkg/blockchain/evm/listener_test.go b/pkg/blockchain/evm/listener_test.go index 3e787b254..8b339dc36 100644 --- a/pkg/blockchain/evm/listener_test.go +++ b/pkg/blockchain/evm/listener_test.go @@ -2,6 +2,7 @@ package evm import ( "context" + "fmt" "math/big" "sync" "sync/atomic" @@ -11,7 +12,6 @@ import ( 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" @@ -44,7 +44,8 @@ func TestNewListener(t *testing.T) { logger := log.NewNoopLogger() addr := common.HexToAddress("0x123") - l := NewListener(addr, mockClient, 1, 100, logger, nil, nil) + eventGetter := new(MockContractEventGetter) + l := NewListener(addr, mockClient, 1, 100, logger, nil, eventGetter) require.NotNil(t, l) assert.Equal(t, addr, l.contractAddress) assert.Equal(t, uint64(1), l.blockchainID) @@ -57,19 +58,21 @@ func TestListener_Listen_CurrentEvents(t *testing.T) { logger := log.NewNoopLogger() addr := common.HexToAddress("0x123") - // Setup latest event getter (start from 0) - getLatestEvent := func(contractAddress string, networkID uint64) (core.BlockchainEvent, error) { - return core.BlockchainEvent{BlockNumber: 0, LogIndex: 0}, nil - } + eventGetter := new(MockContractEventGetter) + eventGetter.On("GetLatestContractEventBlockNumber", addr.String(), uint64(1)).Return(uint64(0), nil) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) // Channel to signal event handling eventHandled := make(chan struct{}) handleEvent := func(ctx context.Context, log types.Log) error { + cancel() close(eventHandled) return nil } - listener := NewListener(addr, mockClient, 1, 100, logger, handleEvent, getLatestEvent) + listener := NewListener(addr, mockClient, 1, 100, logger, handleEvent, eventGetter) // Mock SubscribeFilterLogs sub := &MockSubscription{ @@ -86,8 +89,8 @@ func TestListener_Listen_CurrentEvents(t *testing.T) { }). Return(sub, nil) - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + // The first current event will trigger IsContractEventPresent check + eventGetter.On("IsContractEventPresent", uint64(1), uint64(10), mock.Anything, uint32(1)).Return(false, nil) go listener.Listen(ctx, func(err error) {}) @@ -105,7 +108,8 @@ func TestListener_ReconcileBlockRange(t *testing.T) { logger := log.NewNoopLogger() addr := common.HexToAddress("0x123") - listener := NewListener(addr, mockClient, 1, 10, logger, nil, nil) + eventGetter := new(MockContractEventGetter) + listener := NewListener(addr, mockClient, 1, 10, logger, nil, eventGetter) // Setup FilterLogs mock // We expect a range fetch. start=100, step=10 -> end=110. current=120. @@ -129,7 +133,7 @@ func TestListener_ReconcileBlockRange(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - listener.reconcileBlockRange(120, 100, 0, historicalCh) + listener.reconcileBlockRange(context.Background(), 120, 100, historicalCh) close(historicalCh) }() @@ -151,9 +155,15 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { addr := common.HexToAddress("0x123") // Start from block 100 - getLatestEvent := func(contractAddress string, networkID uint64) (core.BlockchainEvent, error) { - return core.BlockchainEvent{BlockNumber: 100, LogIndex: 0}, nil - } + eventGetter := new(MockContractEventGetter) + eventGetter.On("GetLatestContractEventBlockNumber", addr.String(), uint64(1)).Return(uint64(100), nil) + // Historical event at block 105 is not present + eventGetter.On("IsContractEventPresent", uint64(1), uint64(105), mock.Anything, uint32(0)).Return(false, nil) + // Current event at block 111 — after historical is done, first current event triggers check + eventGetter.On("IsContractEventPresent", uint64(1), uint64(111), mock.Anything, uint32(0)).Return(false, nil) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) var receivedCount int64 doneCh := make(chan struct{}) @@ -161,6 +171,7 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { handleEvent := func(ctx context.Context, log types.Log) error { count := atomic.AddInt64(&receivedCount, 1) if count >= 2 { // Expect 1 historical + 1 current + cancel() select { case <-doneCh: default: @@ -171,7 +182,7 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { return nil } - listener := NewListener(addr, mockClient, 1, 10, logger, handleEvent, getLatestEvent) + listener := NewListener(addr, mockClient, 1, 10, logger, handleEvent, eventGetter) // Mock HeaderByNumber (current tip is 110) currentHeader := &types.Header{Number: big.NewInt(110)} @@ -191,9 +202,6 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { }). Return(sub, nil) - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - go listener.Listen(ctx, func(err error) {}) select { @@ -203,3 +211,130 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { t.Fatal("timeout waiting for events") } } + +func TestProcessEvents_DedupSkipsPresent(t *testing.T) { + t.Parallel() + logger := log.NewNoopLogger() + addr := common.HexToAddress("0x123") + eventGetter := new(MockContractEventGetter) + + var handledBlocks []uint64 + handleEvent := func(ctx context.Context, eventLog types.Log) error { + handledBlocks = append(handledBlocks, eventLog.BlockNumber) + return nil + } + + listener := NewListener(addr, new(MockEVMClient), 1, 10, logger, handleEvent, eventGetter) + + // Historical: 3 events. First 2 are present (skipped), 3rd is not (handled). + // After the 3rd, the check should stop — no IsContractEventPresent call for events 4+. + historicalCh := make(chan types.Log, 5) + historicalCh <- types.Log{BlockNumber: 100, Index: 0, TxHash: common.HexToHash("0xaa")} + historicalCh <- types.Log{BlockNumber: 101, Index: 0, TxHash: common.HexToHash("0xbb")} + historicalCh <- types.Log{BlockNumber: 102, Index: 0, TxHash: common.HexToHash("0xcc")} + historicalCh <- types.Log{BlockNumber: 103, Index: 0, TxHash: common.HexToHash("0xdd")} + historicalCh <- types.Log{BlockNumber: 104, Index: 0, TxHash: common.HexToHash("0xee")} + close(historicalCh) + + // First two are present, third is not + eventGetter.On("IsContractEventPresent", uint64(1), uint64(100), mock.Anything, uint32(0)).Return(true, nil).Once() + eventGetter.On("IsContractEventPresent", uint64(1), uint64(101), mock.Anything, uint32(0)).Return(true, nil).Once() + eventGetter.On("IsContractEventPresent", uint64(1), uint64(102), mock.Anything, uint32(0)).Return(false, nil).Once() + // No mock for 103, 104 — if called, mock will panic, proving the check stopped + + sub := &MockSubscription{errChan: make(chan error)} + currentCh := make(chan types.Log) + + // processEvents will drain historical, then block on currentCh. Cancel via ctx. + ctx, cancel := context.WithCancel(context.Background()) + + go func() { + // Wait for historical processing, then cancel + time.Sleep(50 * time.Millisecond) + cancel() + }() + + var lastBlock uint64 + err := listener.processEvents(ctx, sub, historicalCh, currentCh, &lastBlock) + require.NoError(t, err) + + // Only events 102, 103, 104 should have been handled (100, 101 skipped as present) + assert.Equal(t, []uint64{102, 103, 104}, handledBlocks) + eventGetter.AssertExpectations(t) +} + +func TestProcessEvents_SubscriptionErrorDuringPhase1(t *testing.T) { + t.Parallel() + logger := log.NewNoopLogger() + addr := common.HexToAddress("0x123") + eventGetter := new(MockContractEventGetter) + + var handledBlocks []uint64 + handleEvent := func(ctx context.Context, eventLog types.Log) error { + handledBlocks = append(handledBlocks, eventLog.BlockNumber) + return nil + } + + listener := NewListener(addr, new(MockEVMClient), 1, 10, logger, handleEvent, eventGetter) + + // Historical channel with events that will block (not closed yet) + historicalCh := make(chan types.Log, 2) + historicalCh <- types.Log{BlockNumber: 100, Index: 0, TxHash: common.HexToHash("0xaa")} + + eventGetter.On("IsContractEventPresent", uint64(1), uint64(100), mock.Anything, uint32(0)).Return(false, nil) + + // Subscription that will error shortly + subErrCh := make(chan error, 1) + sub := &MockSubscription{errChan: subErrCh, unsub: func() {}} + currentCh := make(chan types.Log) + + // Send subscription error after a short delay (while historical is still open) + go func() { + time.Sleep(50 * time.Millisecond) + subErrCh <- fmt.Errorf("connection lost") + }() + + var lastBlock uint64 + err := listener.processEvents(context.Background(), sub, historicalCh, currentCh, &lastBlock) + + // Should return nil (reconnect signal), not an error + require.NoError(t, err) + // The first historical event should have been handled before the subscription error + assert.Equal(t, []uint64{100}, handledBlocks) +} + +func TestReconcileBlockRange_ContextCancellation(t *testing.T) { + t.Parallel() + mockClient := new(MockEVMClient) + logger := log.NewNoopLogger() + addr := common.HexToAddress("0x123") + eventGetter := new(MockContractEventGetter) + + listener := NewListener(addr, mockClient, 1, 10, logger, nil, eventGetter) + + ctx, cancel := context.WithCancel(context.Background()) + + // First batch succeeds but cancels the context during the call. + // The second batch should never be reached. + logs1 := []types.Log{{BlockNumber: 105, Index: 0}} + mockClient.On("FilterLogs", mock.Anything, mock.MatchedBy(func(q ethereum.FilterQuery) bool { + return q.FromBlock.Uint64() == 100 && q.ToBlock.Uint64() == 110 + })).Run(func(args mock.Arguments) { + cancel() + }).Return(logs1, nil) + + historicalCh := make(chan types.Log, 10) + listener.reconcileBlockRange(ctx, 200, 100, historicalCh) + close(historicalCh) + + // Drain whatever was sent before cancellation took effect + var received []types.Log + for l := range historicalCh { + received = append(received, l) + } + + // The event from the first batch may or may not have been sent (race between + // the ctx.Done select and the historicalCh send), but the second batch must not run. + assert.LessOrEqual(t, len(received), 1) + mockClient.AssertNumberOfCalls(t, "FilterLogs", 1) +} diff --git a/pkg/blockchain/evm/locking_client.go b/pkg/blockchain/evm/locking_client.go index 56a49dcd0..52c9a2cca 100644 --- a/pkg/blockchain/evm/locking_client.go +++ b/pkg/blockchain/evm/locking_client.go @@ -1,8 +1,6 @@ package evm import ( - "math/big" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" @@ -100,7 +98,7 @@ func (c *LockingClient) Lock(targetWalletAddress string, amount decimal.Decimal) return "", errors.New("transaction signer not configured") } - amountBig, err := core.DecimalToBigInt(amount, c.tokenDecimals) + amountBig, err := core.DecimalToUint256(amount, c.tokenDecimals) if err != nil { return "", errors.Wrap(err, "failed to convert amount with decimal precision") } @@ -182,7 +180,7 @@ func (c *LockingClient) ApproveToken(amount decimal.Decimal) (string, error) { return "", errors.New("transaction signer not configured") } - amountBig, err := core.DecimalToBigInt(amount, c.tokenDecimals) + amountBig, err := core.DecimalToUint256(amount, c.tokenDecimals) if err != nil { return "", errors.Wrap(err, "failed to convert amount with decimal precision") } @@ -224,9 +222,3 @@ func (c *LockingClient) GetBalance(user string) (decimal.Decimal, error) { 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 index 3493cd3f5..f3baca2b0 100644 --- a/pkg/blockchain/evm/locking_reactor.go +++ b/pkg/blockchain/evm/locking_reactor.go @@ -15,6 +15,26 @@ import ( "github.com/layer-3/nitrolite/pkg/log" ) +// LockingContractReactorStoreTxHandler 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 LockingContractReactorStoreTxHandler func(LockingContractReactorStore) error + +// LockingContractReactorStoreTxProvider wraps Store operations in a database transaction. +// It accepts a LockingContractReactorStoreTxHandler and manages transaction lifecycle (begin, commit, rollback). +// Returns an error if the handler fails or the transaction cannot be committed. +type LockingContractReactorStoreTxProvider func(LockingContractReactorStoreTxHandler) error + +// LockingContractReactorStore defines the persistence layer interface for channel and state data. +// All methods should be implemented to work within database transactions. +// Implementations are typically provided by the database layer and wrapped by LockingContractReactorStoreTxProvider. +type LockingContractReactorStore interface { + // UpdateUserStaked updates the total staked amount for a user on a specific blockchain. + UpdateUserStaked(wallet string, blockchainID uint64, amount decimal.Decimal) error + + // StoreContractEvent persists a processed contract event to the database, ensuring it's recorded in the same transaction as state updates. + StoreContractEvent(ev core.BlockchainEvent) error +} + var lockingContractAbi *abi.ABI var lockingContractFilterer *AppRegistryFilterer var eventMapping map[common.Hash]string @@ -37,23 +57,23 @@ func initLockingContract() { } type LockingContractReactor struct { - blockchainID uint64 - eventHandler core.LockingContractEventHandler - storeContractEvent StoreContractEvent - tokenDecimals int32 - onEventProcessed func(blockchainID uint64, success bool) + blockchainID uint64 + eventHandler core.LockingContractEventHandler + useStoreInTx LockingContractReactorStoreTxProvider + tokenDecimals int32 + onEventProcessed func(blockchainID uint64, success bool) } -func NewLockingContractReactor(blockchainID uint64, eventHandler core.LockingContractEventHandler, getTokenDecimals func() (uint8, error), storeContractEvent StoreContractEvent) (*LockingContractReactor, error) { +func NewLockingContractReactor(blockchainID uint64, eventHandler core.LockingContractEventHandler, getTokenDecimals func() (uint8, error), useStoreInTx LockingContractReactorStoreTxProvider) (*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, + blockchainID: blockchainID, + eventHandler: eventHandler, + tokenDecimals: int32(tokenDecimals), + useStoreInTx: useStoreInTx, }, nil } @@ -73,44 +93,47 @@ func (r *LockingContractReactor) HandleEvent(ctx context.Context, l types.Log) e } 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()) - } + err := r.useStoreInTx(func(store LockingContractReactorStore) error { + var err error + switch eventID { + case lockingContractAbi.Events["Locked"].ID: + err = r.handleLocked(ctx, store, l) + case lockingContractAbi.Events["Relocked"].ID: + err = r.handleRelocked(ctx, store, l) + case lockingContractAbi.Events["UnlockInitiated"].ID: + err = r.handleUnlockInitiated(ctx, store, l) + case lockingContractAbi.Events["Withdrawn"].ID: + err = r.handleWithdrawn(ctx, store, l) + default: + logger.Warn("unknown event: " + eventID.Hex()) + } + if err != nil { + logger.Warn("error processing event", "error", err) + return errors.Wrap(err, "error processing event") + } + + if err := store.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 + }) 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 + return err } -func (r *LockingContractReactor) handleLocked(ctx context.Context, l types.Log) error { +func (r *LockingContractReactor) handleLocked(ctx context.Context, store LockingContractReactorStore, l types.Log) error { event, err := lockingContractFilterer.ParseLocked(l) if err != nil { return errors.Wrap(err, "failed to parse Locked event") @@ -121,10 +144,10 @@ func (r *LockingContractReactor) handleLocked(ctx context.Context, l types.Log) BlockchainID: r.blockchainID, Balance: decimal.NewFromBigInt(event.NewBalance, -r.tokenDecimals), } - return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, store, &ev) } -func (r *LockingContractReactor) handleRelocked(ctx context.Context, l types.Log) error { +func (r *LockingContractReactor) handleRelocked(ctx context.Context, store LockingContractReactorStore, l types.Log) error { event, err := lockingContractFilterer.ParseRelocked(l) if err != nil { return errors.Wrap(err, "failed to parse Relocked event") @@ -134,10 +157,10 @@ func (r *LockingContractReactor) handleRelocked(ctx context.Context, l types.Log BlockchainID: r.blockchainID, Balance: decimal.NewFromBigInt(event.Balance, -r.tokenDecimals), } - return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, store, &ev) } -func (r *LockingContractReactor) handleUnlockInitiated(ctx context.Context, l types.Log) error { +func (r *LockingContractReactor) handleUnlockInitiated(ctx context.Context, store LockingContractReactorStore, l types.Log) error { event, err := lockingContractFilterer.ParseUnlockInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse Unlockinitiated event") @@ -147,10 +170,10 @@ func (r *LockingContractReactor) handleUnlockInitiated(ctx context.Context, l ty BlockchainID: r.blockchainID, Balance: decimal.Zero, } - return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, store, &ev) } -func (r *LockingContractReactor) handleWithdrawn(ctx context.Context, l types.Log) error { +func (r *LockingContractReactor) handleWithdrawn(ctx context.Context, store LockingContractReactorStore, l types.Log) error { event, err := lockingContractFilterer.ParseWithdrawn(l) if err != nil { return errors.Wrap(err, "failed to parse Withdrawn event") @@ -160,5 +183,5 @@ func (r *LockingContractReactor) handleWithdrawn(ctx context.Context, l types.Lo BlockchainID: r.blockchainID, Balance: decimal.Zero, } - return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, store, &ev) } diff --git a/pkg/blockchain/evm/locking_reactor_test.go b/pkg/blockchain/evm/locking_reactor_test.go index a03389e0f..3a5a6d61f 100644 --- a/pkg/blockchain/evm/locking_reactor_test.go +++ b/pkg/blockchain/evm/locking_reactor_test.go @@ -9,11 +9,27 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/layer-3/nitrolite/pkg/core" ) +// mockLockingStore implements LockingContractReactorStore for testing +type mockLockingStore struct { + mock.Mock +} + +func (m *mockLockingStore) UpdateUserStaked(wallet string, blockchainID uint64, amount decimal.Decimal) error { + args := m.Called(wallet, blockchainID, amount) + return args.Error(0) +} + +func (m *mockLockingStore) StoreContractEvent(ev core.BlockchainEvent) error { + args := m.Called(ev) + return args.Error(0) +} + func TestAppRegistryReactor_HandleLocked(t *testing.T) { userAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") deposited := big.NewInt(500_000_000) // 500 USDC (6 decimals) @@ -39,23 +55,27 @@ func TestAppRegistryReactor_HandleLocked(t *testing.T) { } t.Run("success", func(t *testing.T) { + store := new(mockLockingStore) var capturedEvent *core.UserLockedBalanceUpdatedEvent handler := &mockAppRegistryEventHandler{ - handleFn: func(_ context.Context, ev *core.UserLockedBalanceUpdatedEvent) error { + handleFn: func(_ context.Context, _ core.LockingContractEventHandlerStore, ev *core.UserLockedBalanceUpdatedEvent) error { capturedEvent = ev return nil }, } - var storedEvent core.BlockchainEvent - storeContractEvent := func(ev core.BlockchainEvent) error { - storedEvent = ev - return nil + useStoreInTx := func(handler LockingContractReactorStoreTxHandler) error { + return handler(store) } + // Expect StoreContractEvent to be called + store.On("StoreContractEvent", mock.MatchedBy(func(ev core.BlockchainEvent) bool { + return ev.Name == "Locked" && ev.BlockNumber == 100 && ev.BlockchainID == blockchainID + })).Return(nil) + reactor, err := NewLockingContractReactor(blockchainID, handler, func() (uint8, error) { return uint8(tokenDecimals), nil - }, storeContractEvent) + }, useStoreInTx) require.NoError(t, err) var processedSuccess bool @@ -72,40 +92,43 @@ func TestAppRegistryReactor_HandleLocked(t *testing.T) { 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) + store.AssertExpectations(t) }) t.Run("getTokenDecimals error", func(t *testing.T) { handler := &mockAppRegistryEventHandler{ - handleFn: func(_ context.Context, _ *core.UserLockedBalanceUpdatedEvent) error { + handleFn: func(_ context.Context, _ core.LockingContractEventHandlerStore, _ *core.UserLockedBalanceUpdatedEvent) error { t.Fatal("handler should not be called") return nil }, } - storeContractEvent := func(_ core.BlockchainEvent) error { return nil } + useStoreInTx := func(handler LockingContractReactorStoreTxHandler) error { + return handler(nil) + } _, err := NewLockingContractReactor(blockchainID, handler, func() (uint8, error) { return 0, assert.AnError - }, storeContractEvent) + }, useStoreInTx) require.Error(t, err) assert.Contains(t, err.Error(), "failed to get token decimals") }) t.Run("handler error", func(t *testing.T) { + store := new(mockLockingStore) handler := &mockAppRegistryEventHandler{ - handleFn: func(_ context.Context, _ *core.UserLockedBalanceUpdatedEvent) error { + handleFn: func(_ context.Context, _ core.LockingContractEventHandlerStore, _ *core.UserLockedBalanceUpdatedEvent) error { return assert.AnError }, } - storeContractEvent := func(_ core.BlockchainEvent) error { return nil } + useStoreInTx := func(handler LockingContractReactorStoreTxHandler) error { + return handler(store) + } reactor, err := NewLockingContractReactor(blockchainID, handler, func() (uint8, error) { return uint8(tokenDecimals), nil - }, storeContractEvent) + }, useStoreInTx) require.NoError(t, err) var processedSuccess bool @@ -119,9 +142,9 @@ func TestAppRegistryReactor_HandleLocked(t *testing.T) { } type mockAppRegistryEventHandler struct { - handleFn func(context.Context, *core.UserLockedBalanceUpdatedEvent) error + handleFn func(context.Context, core.LockingContractEventHandlerStore, *core.UserLockedBalanceUpdatedEvent) error } -func (m *mockAppRegistryEventHandler) HandleUserLockedBalanceUpdated(ctx context.Context, ev *core.UserLockedBalanceUpdatedEvent) error { - return m.handleFn(ctx, ev) +func (m *mockAppRegistryEventHandler) HandleUserLockedBalanceUpdated(ctx context.Context, tx core.LockingContractEventHandlerStore, ev *core.UserLockedBalanceUpdatedEvent) error { + return m.handleFn(ctx, tx, ev) } diff --git a/pkg/blockchain/evm/mock_test.go b/pkg/blockchain/evm/mock_test.go index 4f72abbbf..b96784f80 100644 --- a/pkg/blockchain/evm/mock_test.go +++ b/pkg/blockchain/evm/mock_test.go @@ -120,6 +120,21 @@ func (m *MockEVMClient) SubscribeFilterLogs(ctx context.Context, query ethereum. return args.Get(0).(ethereum.Subscription), args.Error(1) } +// MockContractEventGetter implements ContractEventGetter interface +type MockContractEventGetter struct { + mock.Mock +} + +func (m *MockContractEventGetter) GetLatestContractEventBlockNumber(contractAddress string, blockchainID uint64) (uint64, error) { + args := m.Called(contractAddress, blockchainID) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockContractEventGetter) IsContractEventPresent(blockchainID, blockNumber uint64, txHash string, logIndex uint32) (bool, error) { + args := m.Called(blockchainID, blockNumber, txHash, logIndex) + return args.Bool(0), args.Error(1) +} + // MockAssetStore implements AssetStore interface type MockAssetStore struct { mock.Mock @@ -139,3 +154,8 @@ func (m *MockAssetStore) GetTokenAddress(asset string, blockchainID uint64) (str args := m.Called(asset, blockchainID) return args.String(0), args.Error(1) } + +func (m *MockAssetStore) GetTokenAsset(blockchainID uint64, tokenAddress string) (string, error) { + args := m.Called(blockchainID, tokenAddress) + return args.String(0), args.Error(1) +} diff --git a/pkg/blockchain/evm/utils.go b/pkg/blockchain/evm/utils.go index c118d5311..a4b6f2d50 100644 --- a/pkg/blockchain/evm/utils.go +++ b/pkg/blockchain/evm/utils.go @@ -10,7 +10,6 @@ import ( "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" @@ -47,17 +46,17 @@ func signerTxOpts(signer sign.Signer, blockchainID uint64) *bind.TransactOpts { } } -// waitForBackOffTimeout implements exponential backoff between retries -func waitForBackOffTimeout(logger log.Logger, backOffCount int, originator string) { +// backOffDuration returns the exponential backoff delay for the given attempt count. +// Returns 0 when backOffCount is 0 (no delay). +// Returns -1 when backOffCount exceeds maxBackOffCount (caller should abort). +func backOffDuration(backOffCount int) time.Duration { if backOffCount > maxBackOffCount { - logger.Fatal("back off limit reached, exiting", "originator", originator, "backOffCollisionCount", backOffCount) - return + return -1 } - - if backOffCount > 0 { - logger.Info("backing off", "originator", originator, "backOffCollisionCount", backOffCount) - <-time.After(time.Duration(2^backOffCount-1) * time.Second) + if backOffCount == 0 { + return 0 } + return time.Duration((1< 0 the function first fetches all matching logs +// from fromBlock to the current chain head before switching to live events. This +// fills the gap between the last processed block and "now", ensuring no event is +// missed during a reconnect. Pass fromBlock = 0 to skip historical fetch (e.g. on +// the very first call when no prior state exists). +// +// The channel is closed when ctx is cancelled or the underlying subscription is +// lost. Callers should treat a closed channel as a signal to resubscribe, passing +// the BlockNumber of the last received event + 1 as fromBlock to avoid gaps. +// +// Reorg safety: logs marked Removed (chain reorganisation) are silently skipped. +// +// client must support event subscriptions (WebSocket or IPC transport). +func WatchValidatorRegistered(ctx context.Context, contractAddress common.Address, client EVMClient, blockchainID uint64, fromBlock uint64) (<-chan *core.ValidatorRegisteredEvent, error) { + logger := log.FromContext(ctx).WithName("evm") + + topic := channelHubAbi.Events["ValidatorRegistered"].ID + + query := ethereum.FilterQuery{ + Addresses: []common.Address{contractAddress}, + Topics: [][]common.Hash{{topic}}, + } + + logCh := make(chan types.Log, 16) + sub, err := client.SubscribeFilterLogs(ctx, query, logCh) + if err != nil { + return nil, errors.Wrap(err, "failed to subscribe to ValidatorRegistered events (ensure a WebSocket RPC endpoint is configured)") + } + + eventCh := make(chan *core.ValidatorRegisteredEvent, 16) + + go func() { + defer close(eventCh) + defer sub.Unsubscribe() + + // headBlock is the upper bound of the historical getLogs query. Any live + // event with BlockNumber ≤ headBlock was already fetched historically and + // must be skipped to prevent duplicate delivery from the subscription-to- + // getLogs transition window (the subscription starts before HeaderByNumber + // returns, so logs in that window land in both streams). + var headBlock uint64 + + // Historical phase: fetch logs from fromBlock to the current head before + // processing live events, so reconnects don't miss events emitted during + // any outage window. + if fromBlock > 0 { + header, err := client.HeaderByNumber(ctx, nil) + if err != nil { + if ctx.Err() == nil { + logger.Warn("failed to get chain head for historical ValidatorRegistered fetch — skipping gap fill", "error", err) + } + } else { + headBlock = header.Number.Uint64() + histQuery := ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(fromBlock), + ToBlock: header.Number, + Addresses: []common.Address{contractAddress}, + Topics: [][]common.Hash{{topic}}, + } + histLogs, err := client.FilterLogs(ctx, histQuery) + if err != nil { + if ctx.Err() == nil { + logger.Warn("failed to fetch historical ValidatorRegistered logs — gap fill incomplete", "error", err, "fromBlock", fromBlock) + } + } else { + logger.Info("replaying historical ValidatorRegistered logs", "count", len(histLogs), "fromBlock", fromBlock, "toBlock", header.Number) + for _, l := range histLogs { + if ev := parseAndSend(ctx, eventCh, l, blockchainID, logger); ev == nil { + return // ctx cancelled during delivery + } + } + } + } + } + + // Live phase. + for { + select { + case <-ctx.Done(): + return + case err := <-sub.Err(): + // Suppress the error log on clean ctx cancellation: go-ethereum delivers + // a cancellation error on sub.Err() before ctx.Done() is scheduled, + // which would otherwise log a spurious "subscription error" on shutdown. + if err != nil && ctx.Err() == nil { + logger.Error("ValidatorRegistered subscription error", "error", err, "contract", contractAddress.Hex(), "blockchainID", blockchainID) + } + return + case l, ok := <-logCh: + if !ok { + return + } + if l.Removed { + logger.Warn("skipping removed ValidatorRegistered log (reorg)", "blockchainID", blockchainID, "txHash", l.TxHash.Hex()) + continue + } + // Skip events already covered by the historical getLogs query to + // prevent duplicate delivery from the subscription overlap window. + if l.BlockNumber <= headBlock { + continue + } + if parseAndSend(ctx, eventCh, l, blockchainID, logger) == nil { + return + } + } + } + }() + + return eventCh, nil +} + +// parseAndSend parses a ValidatorRegistered log and forwards it to eventCh. +// Returns the event on success, nil if ctx was cancelled before delivery. +func parseAndSend(ctx context.Context, eventCh chan<- *core.ValidatorRegisteredEvent, l types.Log, blockchainID uint64, logger log.Logger) *core.ValidatorRegisteredEvent { + parsed, err := channelHubFilterer.ParseValidatorRegistered(l) + if err != nil { + logger.Error("failed to parse ValidatorRegistered log", "error", err, "txHash", l.TxHash.Hex()) + return &core.ValidatorRegisteredEvent{} // non-nil signals caller to continue + } + ev := &core.ValidatorRegisteredEvent{ + BlockchainID: blockchainID, + ValidatorID: parsed.ValidatorId, + Validator: parsed.Validator.Hex(), + BlockNumber: l.BlockNumber, + } + logger.Info("ValidatorRegistered event", "blockchainID", blockchainID, "validatorID", ev.ValidatorID, "validator", ev.Validator, "block", ev.BlockNumber) + select { + case eventCh <- ev: + return ev + case <-ctx.Done(): + return nil + } +} diff --git a/pkg/core/README.md b/pkg/core/README.md index 539c1b3a7..466b885df 100644 --- a/pkg/core/README.md +++ b/pkg/core/README.md @@ -1,6 +1,6 @@ # Core Package -The `core` package defines the fundamental domain models, interfaces, and cryptographic utilities for the Clearnode protocol. It serves as the single source of truth for shared data structures between the node, client, and smart contract interactions. +The `core` package defines the fundamental domain models, interfaces, and cryptographic utilities for the Nitronode protocol. It serves as the single source of truth for shared data structures between the node, client, and smart contract interactions. ## Overview diff --git a/pkg/core/channel_signer.go b/pkg/core/channel_signer.go index 36f9360b3..4c7a79c3d 100644 --- a/pkg/core/channel_signer.go +++ b/pkg/core/channel_signer.go @@ -71,6 +71,11 @@ func bitmaskToHex(mask [32]byte) string { } func IsChannelSignerSupported(approvedSigValidators string, signerType ChannelSignerType) bool { + // Default signer is always supported, matching the smart contract behavior. + if signerType == ChannelSignerType_Default { + return true + } + // Mirrors Solidity: (approvedSigValidators >> signerType) & 1 == 1 val, ok := hexToBitmask(approvedSigValidators) if !ok { @@ -173,7 +178,7 @@ func (s *ChannelSigValidator) Recover(data, sig []byte) (string, error) { } // Step 1: Verify participant authorized this session key - // authMessage = _toSigningData(skAuth) = abi.encode(skAuth.sessionKey, skAuth.metadataHash) + // authMessage = toSigningData(skAuth) = abi.encode(SESSION_KEY_AUTH_TYPEHASH, sessionKey, metadataHash) packedAuth, err := PackChannelKeyStateV1(skAuth.SessionKey.Hex(), skAuth.MetadataHash) if err != nil { return "", fmt.Errorf("failed to pack auth data: %w", err) diff --git a/pkg/core/channel_signer_test.go b/pkg/core/channel_signer_test.go new file mode 100644 index 000000000..a9c67f5f7 --- /dev/null +++ b/pkg/core/channel_signer_test.go @@ -0,0 +1,59 @@ +package core + +import "testing" + +func TestIsChannelSignerSupported(t *testing.T) { + tests := []struct { + name string + bitmap string + signerType ChannelSignerType + want bool + }{ + { + name: "default signer supported when set in bitmap", + bitmap: "0x01", + signerType: ChannelSignerType_Default, + want: true, + }, + { + name: "default signer supported even when not in bitmap", + bitmap: "0x02", + signerType: ChannelSignerType_Default, + want: true, + }, + { + name: "default signer supported with empty bitmap", + bitmap: "0x00", + signerType: ChannelSignerType_Default, + want: true, + }, + { + name: "session key supported when set in bitmap", + bitmap: "0x02", + signerType: ChannelSignerType_SessionKey, + want: true, + }, + { + name: "session key not supported when not in bitmap", + bitmap: "0x01", + signerType: ChannelSignerType_SessionKey, + want: false, + }, + { + name: "session key supported when both set in bitmap", + bitmap: "0x03", + signerType: ChannelSignerType_SessionKey, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := IsChannelSignerSupported(tt.bitmap, tt.signerType) + if got != tt.want { + t.Errorf("IsChannelSignerSupported(%q, %v) = %v, want %v", + tt.bitmap, tt.signerType, got, tt.want) + } + }) + } +} diff --git a/pkg/core/event.go b/pkg/core/event.go index 4d2c0bcdb..2ffbc6312 100644 --- a/pkg/core/event.go +++ b/pkg/core/event.go @@ -4,6 +4,13 @@ import "github.com/shopspring/decimal" // On-chain events +// NodeBalanceUpdatedEvent represents the NodeBalanceUpdated event +type NodeBalanceUpdatedEvent struct { + BlockchainID uint64 `json:"blockchain_id"` + Asset string `json:"asset"` + Balance decimal.Decimal `json:"balance"` +} + // HomeChannelCreatedEvent represents the ChannelCreated event type HomeChannelCreatedEvent channelEvent @@ -28,6 +35,13 @@ type EscrowDepositChallengedEvent channelChallengedEvent // EscrowDepositFinalizedEvent represents the EscrowDepositFinalized event type EscrowDepositFinalizedEvent channelEvent +// EscrowDepositsPurgedEvent represents the EscrowDepositsPurged event emitted when expired +// escrow deposits are finalized by the purge queue without a signed FINALIZE_ESCROW_DEPOSIT state. +type EscrowDepositsPurgedEvent struct { + // EscrowIDs holds the hex-encoded escrow IDs (== channel_id in the channels table) that were purged. + EscrowIDs []string `json:"escrow_ids"` +} + // EscrowWithdrawalInitiatedEvent represents the EscrowWithdrawalInitiated event type EscrowWithdrawalInitiatedEvent channelEvent @@ -40,12 +54,17 @@ type EscrowWithdrawalFinalizedEvent channelEvent type channelEvent struct { ChannelID string `json:"channel_id"` StateVersion uint64 `json:"state_version"` + // UserSig is the hex-encoded user signature recovered from the on-chain state payload. + // Empty when the parsed event carries no user signature (e.g. unilateral node-only state). + UserSig string `json:"user_sig,omitempty"` } type channelChallengedEvent struct { ChannelID string `json:"channel_id"` StateVersion uint64 `json:"state_version"` ChallengeExpiry uint64 `json:"challenge_expiry"` + // UserSig is the hex-encoded user signature recovered from the on-chain state payload. + UserSig string `json:"user_sig,omitempty"` } type UserLockedBalanceUpdatedEvent struct { @@ -54,6 +73,19 @@ type UserLockedBalanceUpdatedEvent struct { Balance decimal.Decimal `json:"balance"` } +// ValidatorRegisteredEvent is emitted by ChannelHub when the node registers a new +// signature validator. Users should react to unexpected registrations by revoking +// ERC20 approvals granted to ChannelHub — see contracts/SECURITY.md for details. +type ValidatorRegisteredEvent struct { + BlockchainID uint64 `json:"blockchain_id"` + ValidatorID uint8 `json:"validator_id"` + // Validator is the EIP-55 checksummed hex address of the registered validator contract. + // Always compare using strings.EqualFold or common.HexToAddress(ev.Validator).Hex() + // to avoid silent mismatches against lowercase or non-checksummed config values. + Validator string `json:"validator"` + BlockNumber uint64 `json:"block_number"` // block where the event was emitted; use as fromBlock on reconnect +} + 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 67c9b78e3..0830b94a0 100644 --- a/pkg/core/interface.go +++ b/pkg/core/interface.go @@ -11,9 +11,6 @@ import ( // Client defines the interface for interacting with the ChannelsHub smart contract // TODO: add context to all methods type BlockchainClient interface { - // Getters - IVault - GetAccountsBalances(accounts []string, tokens []string) ([][]decimal.Decimal, error) - // Getters - Token Balance & Approval GetTokenBalance(asset string, walletAddress string) (decimal.Decimal, error) Approve(asset string, amount decimal.Decimal) (string, error) @@ -25,9 +22,9 @@ type BlockchainClient interface { GetEscrowDepositData(escrowChannelID string) (EscrowDepositDataResponse, error) GetEscrowWithdrawalData(escrowChannelID string) (EscrowWithdrawalDataResponse, error) - // IVault functions - Deposit(node, token string, amount decimal.Decimal) (string, error) - Withdraw(node, token string, amount decimal.Decimal) (string, error) + // Node vault functions + Deposit(token string, amount decimal.Decimal) (string, error) + Withdraw(to, token string, amount decimal.Decimal) (string, error) // Node lifecycle EnsureSigValidatorRegistered(validatorID uint8, validatorAddress string, checkOnly bool) error @@ -89,19 +86,110 @@ type AssetStore interface { // Channel lifecycle event handlers type ChannelHubEventHandler interface { - HandleHomeChannelCreated(context.Context, *HomeChannelCreatedEvent) error - HandleHomeChannelMigrated(context.Context, *HomeChannelMigratedEvent) error - HandleHomeChannelCheckpointed(context.Context, *HomeChannelCheckpointedEvent) error - HandleHomeChannelChallenged(context.Context, *HomeChannelChallengedEvent) error - HandleHomeChannelClosed(context.Context, *HomeChannelClosedEvent) error - HandleEscrowDepositInitiated(context.Context, *EscrowDepositInitiatedEvent) error - HandleEscrowDepositChallenged(context.Context, *EscrowDepositChallengedEvent) error - HandleEscrowDepositFinalized(context.Context, *EscrowDepositFinalizedEvent) error - HandleEscrowWithdrawalInitiated(context.Context, *EscrowWithdrawalInitiatedEvent) error - HandleEscrowWithdrawalChallenged(context.Context, *EscrowWithdrawalChallengedEvent) error - HandleEscrowWithdrawalFinalized(context.Context, *EscrowWithdrawalFinalizedEvent) error + HandleNodeBalanceUpdated(context.Context, ChannelHubEventHandlerStore, *NodeBalanceUpdatedEvent) error + HandleHomeChannelCreated(context.Context, ChannelHubEventHandlerStore, *HomeChannelCreatedEvent) error + HandleHomeChannelMigrated(context.Context, ChannelHubEventHandlerStore, *HomeChannelMigratedEvent) error + HandleHomeChannelCheckpointed(context.Context, ChannelHubEventHandlerStore, *HomeChannelCheckpointedEvent) error + HandleHomeChannelChallenged(context.Context, ChannelHubEventHandlerStore, *HomeChannelChallengedEvent) error + HandleHomeChannelClosed(context.Context, ChannelHubEventHandlerStore, *HomeChannelClosedEvent) error + HandleEscrowDepositInitiated(context.Context, ChannelHubEventHandlerStore, *EscrowDepositInitiatedEvent) error + HandleEscrowDepositChallenged(context.Context, ChannelHubEventHandlerStore, *EscrowDepositChallengedEvent) error + HandleEscrowDepositFinalized(context.Context, ChannelHubEventHandlerStore, *EscrowDepositFinalizedEvent) error + HandleEscrowDepositsPurged(context.Context, ChannelHubEventHandlerStore, *EscrowDepositsPurgedEvent) error + HandleEscrowWithdrawalInitiated(context.Context, ChannelHubEventHandlerStore, *EscrowWithdrawalInitiatedEvent) error + HandleEscrowWithdrawalChallenged(context.Context, ChannelHubEventHandlerStore, *EscrowWithdrawalChallengedEvent) error + HandleEscrowWithdrawalFinalized(context.Context, ChannelHubEventHandlerStore, *EscrowWithdrawalFinalizedEvent) error +} + +type ChannelHubEventHandlerStore interface { + // GetLastStateByChannelID retrieves the most recent state for a given channel. + // If signed is true, only returns states with both user and node signatures. + // Returns nil if no matching state exists. + GetLastStateByChannelID(channelID string, signed bool) (*State, error) + + // GetLastUserState retrieves the most recent state for a user's asset across all + // channels and detached chain entries (HomeChannelID nil). Returns nil if no + // matching state exists. If signed is true, only fully co-signed states are returned. + GetLastUserState(wallet, asset string, signed bool) (*State, error) + + // GetStateByChannelIDAndVersion retrieves a specific state version for a channel. + // Returns nil if the state with the specified version does not exist. + GetStateByChannelIDAndVersion(channelID string, version uint64) (*State, error) + + // UpdateChannel persists changes to a channel's metadata (status, version, etc). + // The channel must already exist in the database. + UpdateChannel(channel Channel) error + + // GetChannelByID retrieves a channel by its unique identifier. + // Returns nil if the channel does not exist. + GetChannelByID(channelID string) (*Channel, error) + + // ScheduleCheckpoint schedules a checkpoint operation for a home channel state. + // This queues the state to be submitted on-chain to update the channel's on-chain state. + ScheduleCheckpoint(stateID string, chainID uint64) error + + // ScheduleChallenge schedules a challengeChannel(...) submission on the channel's home + // blockchain using the provided state and a node-produced challenger signature. + ScheduleChallenge(stateID string, chainID uint64) error + + // ScheduleInitiateEscrowDeposit schedules an initiate for an escrow deposit operation. + // This queues the state to be submitted on-chain to finalize an escrow deposit. + ScheduleInitiateEscrowDeposit(stateID string, chainID uint64) error + + // ScheduleFinalizeEscrowDeposit schedules a finalize for an escrow deposit operation. + // This queues the state to be submitted on-chain to finalize an escrow deposit. + ScheduleFinalizeEscrowDeposit(stateID string, chainID uint64) error + + // 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 + + // SetNodeBalance upserts the on-chain liquidity for a given blockchain and asset. + SetNodeBalance(blockchainID uint64, asset string, value decimal.Decimal) error + + // RefreshUserEnforcedBalance recomputes the locked balance from the user's open home channel on-chain state. + RefreshUserEnforcedBalance(wallet, asset string) error + + // LockUserState acquires SELECT ... FOR UPDATE on the user's balance row so the + // caller's transaction serializes against concurrent RPC paths that already lock + // the same row before issuing receiver states. Postgres-only; SQLite is a no-op + // in tests. + LockUserState(wallet, asset string) (decimal.Decimal, error) + + // UpdateStateSigsIfMissing backfills the user and/or node signatures for a stored state + // when the corresponding column is currently NULL. Used to repair the local record after + // an on-chain event proves the state was enforced. Either signature may be empty to skip + // that side; existing values are never overwritten and the call is idempotent on event replay. + UpdateStateSigsIfMissing(channelID string, version uint64, userSig, nodeSig string) error + + // HasSignedFinalize reports whether a node-signed Finalize state exists for the given + // home channel. Used to detect the post-Finalize lifecycle when the channel status + // has been temporarily overwritten by an on-chain challenge. + HasSignedFinalize(channelID string) (bool, error) + + + // SumNetTransitionAmountAfterVersion returns the net effect on the user's + // home-channel balance of transitions stored against channelID strictly above + // minVersion. Receiver credits (TransferReceive, Release) contribute positively; + // sender debits (TransferSend, Commit) contribute negatively. Other transition + // kinds are excluded. Used to compute the ChallengeRescue amount when a + // challenged channel is closed. + SumNetTransitionAmountAfterVersion(channelID string, minVersion uint64) (decimal.Decimal, error) + + // StoreUserState persists a user state row. Used by the event handler to record a + // ChallengeRescue squash state derived from a closed challenged channel. + StoreUserState(state State, applicationID string) error + + // RecordTransaction creates a transaction row linking state transitions. Used by the + // event handler to record the ChallengeRescue transaction associated with the squash. + RecordTransaction(tx Transaction, applicationID string) error } type LockingContractEventHandler interface { - HandleUserLockedBalanceUpdated(context.Context, *UserLockedBalanceUpdatedEvent) error + HandleUserLockedBalanceUpdated(context.Context, LockingContractEventHandlerStore, *UserLockedBalanceUpdatedEvent) error +} + +type LockingContractEventHandlerStore interface { + // 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/pkg/core/session_key.go b/pkg/core/session_key.go index 0f0b91468..4b4eb1625 100644 --- a/pkg/core/session_key.go +++ b/pkg/core/session_key.go @@ -16,12 +16,13 @@ import ( // ChannelSessionKeyStateV1 represents the state of a session key. type ChannelSessionKeyStateV1 struct { // ID Hash(user_address + session_key + version) - UserAddress string `json:"user_address"` // UserAddress is the user wallet address - SessionKey string `json:"session_key"` // SessionKey is the session key address for delegation - Version uint64 `json:"version"` // Version is the version of the session key format - Assets []string `json:"assets"` // Assets associated with this session key - ExpiresAt time.Time `json:"expires_at"` // Expiration time as unix timestamp of this session key - UserSig string `json:"user_sig"` // UserSig is the user's signature over the session key metadata to authorize the registration/update of the session key + UserAddress string `json:"user_address"` // UserAddress is the user wallet address + SessionKey string `json:"session_key"` // SessionKey is the session key address for delegation + Version uint64 `json:"version"` // Version is the version of the session key format + Assets []string `json:"assets"` // Assets associated with this session key + ExpiresAt time.Time `json:"expires_at"` // Expiration time as unix timestamp of this session key + UserSig string `json:"user_sig"` // UserSig is the user's signature over the session key metadata to authorize the registration/update of the session key + SessionKeySig string `json:"session_key_sig"` // SessionKeySig is the session-key holder's signature proving possession of the key being registered. } type VerifyChannelSessionKePermissionsV1 func(walletAddr, sessionKeyAddr, metadataHash string) (bool, error) @@ -71,16 +72,25 @@ func (s *ChannelSessionKeySignerV1) Type() ChannelSignerType { return ChannelSignerType_SessionKey } -// PackChannelKeyStateV1 packs the session key state for signing using ABI encoding. -// This is used to generate a deterministic hash that the user signs when registering/updating a session key. -// The user_sig field is excluded from packing since it is the signature itself. +var sessionKeyAuthTypehash = crypto.Keccak256Hash([]byte("Nitrolite.SessionKey(address sessionKey,bytes32 metadataHash)")) + +// SessionKeyAuthTypehash returns the type hash prepended to the session key authorization payload. +// It matches the Solidity constant SESSION_KEY_AUTH_TYPEHASH and +// prevents unrelated abi.encode(address, bytes32) signatures from being reused as authorizations. +func SessionKeyAuthTypehash() common.Hash { + return sessionKeyAuthTypehash +} + +// PackChannelKeyStateV1 packs the session key authorization payload for signing using ABI encoding. func PackChannelKeyStateV1(sessionKey string, metadataHash common.Hash) ([]byte, error) { args := abi.Arguments{ + {Type: abi.Type{T: abi.FixedBytesTy, Size: 32}}, // SESSION_KEY_AUTH_TYPEHASH {Type: abi.Type{T: abi.AddressTy}}, // session_key {Type: abi.Type{T: abi.FixedBytesTy, Size: 32}}, // hashed metadata } packed, err := args.Pack( + sessionKeyAuthTypehash, common.HexToAddress(sessionKey), metadataHash, ) @@ -91,19 +101,25 @@ func PackChannelKeyStateV1(sessionKey string, metadataHash common.Hash) ([]byte, return packed, nil } -func GetChannelSessionKeyAuthMetadataHashV1(version uint64, assets []string, expiresAt int64) (common.Hash, error) { +// GetChannelSessionKeyAuthMetadataHashV1 hashes the session-key authorization metadata. +// user_address is bound into the hash; together with the session_key already in +// PackChannelKeyStateV1, this binds the signed payload to a single (wallet, session_key) +// pair so signatures cannot be replayed across wallets or session keys. +func GetChannelSessionKeyAuthMetadataHashV1(userAddress string, version uint64, assets []string, expiresAt int64) (common.Hash, error) { stringArrayType, err := abi.NewType("string[]", "", nil) if err != nil { return common.Hash{}, fmt.Errorf("failed to create string array type: %w", err) } metadtataArgs := abi.Arguments{ + {Type: abi.Type{T: abi.AddressTy}}, // user_address {Type: abi.Type{T: abi.UintTy, Size: 64}}, // version {Type: stringArrayType}, // assets {Type: abi.Type{T: abi.UintTy, Size: 64}}, // expires_at (unix timestamp) } packedMetadataArgs, err := metadtataArgs.Pack( + common.HexToAddress(userAddress), version, assets, uint64(expiresAt), @@ -116,8 +132,18 @@ func GetChannelSessionKeyAuthMetadataHashV1(version uint64, assets []string, exp return hashedMetadata, nil } -func ValidateChannelSessionKeyAuthSigV1(state ChannelSessionKeyStateV1) error { - metadataHash, err := GetChannelSessionKeyAuthMetadataHashV1(state.Version, state.Assets, state.ExpiresAt.Unix()) +// ValidateChannelSessionKeyStateV1 verifies both signatures over the registration payload: +// user_sig must recover to state.UserAddress (wallet authorizes the delegation) and +// session_key_sig must recover to state.SessionKey (session-key holder proves possession). +// Both signatures sign the same PackChannelKeyStateV1(session_key, metadataHash) payload; +// session_key binds the packed bytes and user_address binds the metadata hash, so a +// signature minted for one (wallet, session_key) pair cannot be replayed for another. +func ValidateChannelSessionKeyStateV1(state ChannelSessionKeyStateV1) error { + if state.SessionKeySig == "" { + return fmt.Errorf("session_key_sig is required") + } + + metadataHash, err := GetChannelSessionKeyAuthMetadataHashV1(state.UserAddress, state.Version, state.Assets, state.ExpiresAt.Unix()) if err != nil { return fmt.Errorf("failed to get metadata hash: %w", err) } @@ -127,23 +153,33 @@ func ValidateChannelSessionKeyAuthSigV1(state ChannelSessionKeyStateV1) error { return fmt.Errorf("failed to pack session key state: %w", err) } - authSigBytes, err := hexutil.Decode(state.UserSig) - if err != nil { - return fmt.Errorf("failed to decode user signature: %w", err) - } - recoverer, err := sign.NewAddressRecoverer(sign.TypeEthereumMsg) if err != nil { return fmt.Errorf("failed to create address recoverer: %w", err) } - recoveredAddr, err := recoverer.RecoverAddress(packed, authSigBytes) + userSigBytes, err := hexutil.Decode(state.UserSig) if err != nil { - return fmt.Errorf("failed to recover address from signature: %w", err) + return fmt.Errorf("failed to decode user signature: %w", err) + } + recoveredUser, err := recoverer.RecoverAddress(packed, userSigBytes) + if err != nil { + return fmt.Errorf("failed to recover user_sig: %w", err) + } + if !strings.EqualFold(recoveredUser.String(), state.UserAddress) { + return fmt.Errorf("invalid signature: recovered address %s does not match wallet %s", recoveredUser.String(), state.UserAddress) } - if !strings.EqualFold(recoveredAddr.String(), state.UserAddress) { - return fmt.Errorf("invalid signature: recovered address %s does not match wallet %s", recoveredAddr.String(), state.UserAddress) + sessionKeySigBytes, err := hexutil.Decode(state.SessionKeySig) + if err != nil { + return fmt.Errorf("failed to decode session_key_sig: %w", err) + } + recoveredKey, err := recoverer.RecoverAddress(packed, sessionKeySigBytes) + if err != nil { + return fmt.Errorf("failed to recover session_key_sig: %w", err) + } + if !strings.EqualFold(recoveredKey.String(), state.SessionKey) { + return fmt.Errorf("session_key_sig does not match session_key") } return nil diff --git a/pkg/core/session_key_test.go b/pkg/core/session_key_test.go index c21d999d1..72d47d2a8 100644 --- a/pkg/core/session_key_test.go +++ b/pkg/core/session_key_test.go @@ -27,7 +27,7 @@ func TestChannelSessionKeySignerV1(t *testing.T) { expiresAt := time.Now().Add(1 * time.Hour).Unix() // 4. Compute Metadata Hash - metadataHash, err := GetChannelSessionKeyAuthMetadataHashV1(version, assets, expiresAt) + metadataHash, err := GetChannelSessionKeyAuthMetadataHashV1(userAddress, version, assets, expiresAt) require.NoError(t, err) // 5. Pack Data for Authorization (User signs this) @@ -69,58 +69,122 @@ func TestChannelSessionKeySignerV1(t *testing.T) { assert.Equal(t, strings.ToLower(userAddress), strings.ToLower(recoveredWallet)) } -func TestValidateChannelSessionKeyAuthSigV1(t *testing.T) { +func TestValidateChannelSessionKeyStateV1(t *testing.T) { t.Parallel() - // 1. Setup User Wallet userSigner, userAddress := createSigner(t) + sessionSigner, sessionKeyAddr := createSigner(t) - // 2. Setup Session Key - // We just need address for validation logic, not the signer itself unless we sign with it (which we don't for auth sig) - // But let's use createSigner for consistency - _, sessionKeyAddr := createSigner(t) - - // 3. Define State version := uint64(1) assets := []string{"USDC"} expiresAt := time.Now().Add(1 * time.Hour) - // 4. Create valid signature - metadataHash, err := GetChannelSessionKeyAuthMetadataHashV1(version, assets, expiresAt.Unix()) + metadataHash, err := GetChannelSessionKeyAuthMetadataHashV1(userAddress, version, assets, expiresAt.Unix()) require.NoError(t, err) packed, err := PackChannelKeyStateV1(sessionKeyAddr, metadataHash) require.NoError(t, err) - authSig, err := userSigner.Sign(packed) + userSig, err := userSigner.Sign(packed) + require.NoError(t, err) + + sessionKeySig, err := sessionSigner.Sign(packed) require.NoError(t, err) state := ChannelSessionKeyStateV1{ - UserAddress: userAddress, - SessionKey: sessionKeyAddr, - Version: version, - Assets: assets, - ExpiresAt: expiresAt, - UserSig: hexutil.Encode(authSig), + UserAddress: userAddress, + SessionKey: sessionKeyAddr, + Version: version, + Assets: assets, + ExpiresAt: expiresAt, + UserSig: hexutil.Encode(userSig), + SessionKeySig: hexutil.Encode(sessionKeySig), } - // 5. Validate - err = ValidateChannelSessionKeyAuthSigV1(state) - require.NoError(t, err) + require.NoError(t, ValidateChannelSessionKeyStateV1(state)) - // 6. Test Invalid Signature (wrong signer) + // Empty session_key_sig + stateNoKeySig := state + stateNoKeySig.SessionKeySig = "" + err = ValidateChannelSessionKeyStateV1(stateNoKeySig) + require.Error(t, err) + assert.Contains(t, err.Error(), "session_key_sig is required") + + // user_sig signed by wrong wallet wrongSigner, _ := createSigner(t) - wrongSig, err := wrongSigner.Sign(packed) + wrongUserSig, err := wrongSigner.Sign(packed) + require.NoError(t, err) + stateWrongUser := state + stateWrongUser.UserSig = hexutil.Encode(wrongUserSig) + err = ValidateChannelSessionKeyStateV1(stateWrongUser) + require.Error(t, err) + assert.Contains(t, err.Error(), "does not match wallet") + + // session_key_sig signed by wrong key + wrongKeySigner, _ := createSigner(t) + wrongKeySig, err := wrongKeySigner.Sign(packed) require.NoError(t, err) + stateWrongKey := state + stateWrongKey.SessionKeySig = hexutil.Encode(wrongKeySig) + err = ValidateChannelSessionKeyStateV1(stateWrongKey) + require.Error(t, err) + assert.Contains(t, err.Error(), "session_key_sig does not match session_key") + + // Tampered version (hash mismatch on recover) + stateTampered := state + stateTampered.Version = 2 + assert.Error(t, ValidateChannelSessionKeyStateV1(stateTampered)) +} + +// TestValidateChannelSessionKeyStateV1_NoReplay verifies that signatures cannot be replayed +// across (wallet, session_key) pairs. session_key binds the packed payload and user_address +// binds the metadata hash, so substituting either dimension causes signature recovery to +// yield an unrelated address. +func TestValidateChannelSessionKeyStateV1_NoReplay(t *testing.T) { + t.Parallel() + userSignerA, userAddressA := createSigner(t) + _, userAddressB := createSigner(t) + + sessionSignerA, sessionKeyAddrA := createSigner(t) + _, sessionKeyAddrB := createSigner(t) - state.UserSig = hexutil.Encode(wrongSig) - err1 := ValidateChannelSessionKeyAuthSigV1(state) - require.Error(t, err1) - assert.Contains(t, err1.Error(), "does not match wallet") + version := uint64(1) + assets := []string{"USDC"} + expiresAt := time.Now().Add(1 * time.Hour) - // 7. Test Invalid Signature (wrong data) - state.UserSig = hexutil.Encode(authSig) // Reset sig - state.Version = 2 // Change data - assert.Error(t, ValidateChannelSessionKeyAuthSigV1(state)) // Hash mismatch leads to recover address mismatch + metadataHashA, err := GetChannelSessionKeyAuthMetadataHashV1(userAddressA, version, assets, expiresAt.Unix()) + require.NoError(t, err) + packedA, err := PackChannelKeyStateV1(sessionKeyAddrA, metadataHashA) + require.NoError(t, err) + + userSigA, err := userSignerA.Sign(packedA) + require.NoError(t, err) + sessionKeySigA, err := sessionSignerA.Sign(packedA) + require.NoError(t, err) + + stateA := ChannelSessionKeyStateV1{ + UserAddress: userAddressA, + SessionKey: sessionKeyAddrA, + Version: version, + Assets: assets, + ExpiresAt: expiresAt, + UserSig: hexutil.Encode(userSigA), + SessionKeySig: hexutil.Encode(sessionKeySigA), + } + require.NoError(t, ValidateChannelSessionKeyStateV1(stateA)) + + // Cross-session_key replay: substitute sessionKeyAddrB. packed bytes diverge, both + // recoveries yield unrelated addresses. + stateCrossKey := stateA + stateCrossKey.SessionKey = sessionKeyAddrB + err = ValidateChannelSessionKeyStateV1(stateCrossKey) + require.Error(t, err) + + // Cross-wallet replay: substitute userAddressB. metadataHash diverges, packed bytes + // diverge, both recoveries yield unrelated addresses. + stateCrossUser := stateA + stateCrossUser.UserAddress = userAddressB + err = ValidateChannelSessionKeyStateV1(stateCrossUser) + require.Error(t, err) } func TestGenerateSessionKeyStateIDV1(t *testing.T) { @@ -144,6 +208,28 @@ func TestGenerateSessionKeyStateIDV1(t *testing.T) { assert.NotEqual(t, id1, id3) } +// TestPackChannelKeyStateV1_Typehash verifies the SessionKeyAuthTypehash matches the +// Solidity constant SESSION_KEY_AUTH_TYPEHASH in SessionKeyValidator.sol so that +// off-chain authorization payloads are accepted on-chain. +func TestPackChannelKeyStateV1_Typehash(t *testing.T) { + t.Parallel() + expected := common.HexToHash("0x251773da8b8949935ef07284d20cc8605ad7d6f4cf6b5e040ce07dae857f0b6c") + assert.Equal(t, expected, SessionKeyAuthTypehash()) +} + +func TestPackChannelKeyStateV1(t *testing.T) { + t.Parallel() + sessionKey := "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + metadataHash := common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") + + packed, err := PackChannelKeyStateV1(sessionKey, metadataHash) + require.NoError(t, err) + assert.Len(t, packed, 96, "payload must be 96 bytes: typehash || padded address || metadataHash") + + expected := common.FromHex("0x251773da8b8949935ef07284d20cc8605ad7d6f4cf6b5e040ce07dae857f0b6c000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeefabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") + assert.Equal(t, expected, packed) +} + func TestEncodeDecodeChannelSessionKeySignature(t *testing.T) { t.Parallel() skAuth := channelSessionKeyAuthorization{ diff --git a/pkg/core/state_advancer.go b/pkg/core/state_advancer.go index dee5f6514..dfa0b6c09 100644 --- a/pkg/core/state_advancer.go +++ b/pkg/core/state_advancer.go @@ -19,12 +19,18 @@ func NewStateAdvancerV1(assetStore AssetStore) *StateAdvancerV1 { } // ValidateAdvancement validates that the proposed state is a valid advancement of the current state +// +// NOTE: User signature is not validated here +// +// TODO: Add shared JSON fixture suite consumed by both Go and TS test suites to guarantee validation parity func (v *StateAdvancerV1) ValidateAdvancement(currentState, proposedState State) error { expectedState := currentState.NextState() if proposedState.HomeChannelID == nil { return fmt.Errorf("home channel ID cannot be nil") } + // A non-nil state with a nil HomeChannelID is valid — it represents a user who received + // funds but has not yet opened a channel. In that case, adopt the proposed state's channel info. if expectedState.HomeChannelID == nil { expectedState.HomeChannelID = proposedState.HomeChannelID expectedState.HomeLedger.BlockchainID = proposedState.HomeLedger.BlockchainID @@ -60,10 +66,6 @@ func (v *StateAdvancerV1) ValidateAdvancement(currentState, proposedState State) return fmt.Errorf("state ID mismatch: expected=%s, proposed=%s", expectedState.ID, proposedState.ID) } - if proposedState.UserSig == nil { - return fmt.Errorf("user signature is required") - } - newTransition := proposedState.Transition decimals, err := v.assetStore.GetAssetDecimals(proposedState.Asset) @@ -75,8 +77,34 @@ func (v *StateAdvancerV1) ValidateAdvancement(currentState, proposedState State) return fmt.Errorf("invalid amount for asset %s: %w", proposedState.Asset, err) } + switch newTransition.Type { + case TransitionTypeAcknowledgement: + if !newTransition.Amount.IsZero() { + return fmt.Errorf("transition amount must be zero, got %s", newTransition.Amount.String()) + } + case TransitionTypeFinalize: + if newTransition.Amount.IsNegative() { + return fmt.Errorf("transition amount must not be negative, got %s", newTransition.Amount.String()) + } + default: + if !newTransition.Amount.IsPositive() { + return fmt.Errorf("transition amount must be positive, got %s", newTransition.Amount.String()) + } + } + lastTransition := currentState.Transition + switch lastTransition.Type { + case TransitionTypeMutualLock: + if newTransition.Type != TransitionTypeEscrowDeposit { + return fmt.Errorf("after mutual lock, only escrow deposit is allowed, got: %d", newTransition.Type) + } + case TransitionTypeEscrowLock: + if newTransition.Type != TransitionTypeEscrowWithdraw { + return fmt.Errorf("after escrow lock, only escrow withdraw is allowed, got: %d", newTransition.Type) + } + } + switch newTransition.Type { case TransitionTypeVoid: return fmt.Errorf("cannot apply void transition as new transition") @@ -144,10 +172,14 @@ func (v *StateAdvancerV1) ValidateAdvancement(currentState, proposedState State) return fmt.Errorf("new transition does not match expected: %w", err) } - if err := proposedState.HomeLedger.Equal(expectedState.HomeLedger); err != nil { + if err := expectedState.HomeLedger.Equal(proposedState.HomeLedger); err != nil { return fmt.Errorf("home ledger mismatch: %w", err) } - if err := proposedState.HomeLedger.Validate(); err != nil { + homeDecimals, err := v.assetStore.GetTokenDecimals(proposedState.HomeLedger.BlockchainID, proposedState.HomeLedger.TokenAddress) + if err != nil { + return fmt.Errorf("failed to get home token decimals: %w", err) + } + if err := proposedState.HomeLedger.Validate(homeDecimals); err != nil { return fmt.Errorf("invalid home ledger: %w", err) } @@ -166,10 +198,14 @@ func (v *StateAdvancerV1) ValidateAdvancement(currentState, proposedState State) } if expectedState.EscrowLedger != nil && proposedState.EscrowLedger != nil { - if err := proposedState.EscrowLedger.Equal(*expectedState.EscrowLedger); err != nil { + if err := expectedState.EscrowLedger.Equal(*proposedState.EscrowLedger); err != nil { return fmt.Errorf("escrow ledger mismatch: %w", err) } - if err := proposedState.EscrowLedger.Validate(); err != nil { + escrowDecimals, err := v.assetStore.GetTokenDecimals(proposedState.EscrowLedger.BlockchainID, proposedState.EscrowLedger.TokenAddress) + if err != nil { + return fmt.Errorf("failed to get escrow token decimals: %w", err) + } + if err := proposedState.EscrowLedger.Validate(escrowDecimals); err != nil { return fmt.Errorf("invalid escrow ledger: %w", err) } diff --git a/pkg/core/state_advancer_test.go b/pkg/core/state_advancer_test.go new file mode 100644 index 000000000..1a7426ed8 --- /dev/null +++ b/pkg/core/state_advancer_test.go @@ -0,0 +1,371 @@ +package core + +import ( + "math/big" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newMutualLockState(t *testing.T, amount decimal.Decimal) *State { + t.Helper() + userWallet := "0xUser" + asset := "USDC" + chanID := "0xHomeChannelId" + + state := NewVoidState(asset, userWallet) + state.Version = 5 + state.HomeChannelID = &chanID + state.ID = GetStateID(userWallet, asset, 0, 5) + state.HomeLedger.TokenAddress = "0xToken" + state.HomeLedger.BlockchainID = 1 + + _, err := state.ApplyMutualLockTransition(2, "0xForeignToken", amount) + require.NoError(t, err) + + sig := "0xSig" + state.UserSig = &sig + state.NodeSig = &sig + + return state +} + +func newEscrowLockState(t *testing.T, amount decimal.Decimal) *State { + t.Helper() + userWallet := "0xUser" + asset := "USDC" + chanID := "0xHomeChannelId" + + state := NewVoidState(asset, userWallet) + state.Version = 5 + state.HomeChannelID = &chanID + state.ID = GetStateID(userWallet, asset, 0, 5) + state.HomeLedger.TokenAddress = "0xToken" + state.HomeLedger.BlockchainID = 1 + state.HomeLedger.UserBalance = amount + + _, err := state.ApplyEscrowLockTransition(2, "0xForeignToken", amount) + require.NoError(t, err) + + sig := "0xSig" + state.UserSig = &sig + state.NodeSig = &sig + + return state +} + +func TestValidateAdvancement_StrictTransitionOrdering(t *testing.T) { + t.Parallel() + + advancer := NewStateAdvancerV1(newMockAssetStore()) + amount := decimal.NewFromInt(10) + sig := "0xSig" + + t.Run("reject_non_escrow_deposit_after_mutual_lock", func(t *testing.T) { + t.Parallel() + mutualLockState := newMutualLockState(t, amount) + + proposed := mutualLockState.NextState() + _, err := proposed.ApplyHomeDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*mutualLockState, *proposed) + assert.Error(t, err) + assert.Contains(t, err.Error(), "after mutual lock, only escrow deposit is allowed") + }) + + t.Run("reject_non_escrow_withdraw_after_escrow_lock", func(t *testing.T) { + t.Parallel() + escrowLockState := newEscrowLockState(t, amount) + + proposed := escrowLockState.NextState() + _, err := proposed.ApplyHomeDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*escrowLockState, *proposed) + assert.Error(t, err) + assert.Contains(t, err.Error(), "after escrow lock, only escrow withdraw is allowed") + }) +} + +func TestValidateAdvancement_EscrowDeposit(t *testing.T) { + t.Parallel() + + advancer := NewStateAdvancerV1(newMockAssetStore()) + amount := decimal.NewFromInt(10) + sig := "0xSig" + + t.Run("success_valid_escrow_deposit", func(t *testing.T) { + t.Parallel() + mutualLockState := newMutualLockState(t, amount) + + proposed := mutualLockState.NextState() + _, err := proposed.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*mutualLockState, *proposed) + assert.NoError(t, err) + }) + + t.Run("reject_non_zero_home_node_balance", func(t *testing.T) { + t.Parallel() + mutualLockState := newMutualLockState(t, amount) + + proposed := mutualLockState.NextState() + _, err := proposed.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + proposed.HomeLedger.NodeBalance = amount + + err = advancer.ValidateAdvancement(*mutualLockState, *proposed) + assert.Error(t, err) + assert.Contains(t, err.Error(), "home ledger mismatch") + }) + + t.Run("reject_increased_home_node_net_flow", func(t *testing.T) { + t.Parallel() + mutualLockState := newMutualLockState(t, amount) + + proposed := mutualLockState.NextState() + _, err := proposed.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + proposed.HomeLedger.NodeNetFlow = proposed.HomeLedger.NodeNetFlow.Add(amount) + + err = advancer.ValidateAdvancement(*mutualLockState, *proposed) + assert.Error(t, err) + assert.Contains(t, err.Error(), "home ledger mismatch") + }) + + t.Run("reject_amount_mismatch_with_mutual_lock", func(t *testing.T) { + t.Parallel() + mutualLockState := newMutualLockState(t, amount) + + proposed := mutualLockState.NextState() + _, err := proposed.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + // Attempt escrow deposit with a different amount than the mutual lock. + proposed.Transition.Amount = decimal.NewFromInt(99) + + err = advancer.ValidateAdvancement(*mutualLockState, *proposed) + assert.Error(t, err) + assert.Contains(t, err.Error(), "escrow deposit amount must be the same as mutual lock amount") + }) + + t.Run("reject_escrow_deposit_not_after_mutual_lock", func(t *testing.T) { + t.Parallel() + mutualLockState := newMutualLockState(t, amount) + + homeDepositState := mutualLockState.NextState() + _, err := homeDepositState.ApplyHomeDepositTransition(amount) + require.NoError(t, err) + homeDepositState.UserSig = &sig + + proposed := homeDepositState.NextState() + _, err = proposed.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*homeDepositState, *proposed) + assert.Error(t, err) + assert.Contains(t, err.Error(), "escrow deposit transition must follow a mutual lock transition") + }) +} + +func TestValidateAdvancement_RejectsInvalidAmount(t *testing.T) { + t.Parallel() + + advancer := NewStateAdvancerV1(newMockAssetStore()) + sig := "0xSig" + chanID := "0xChannel" + + newCurrentState := func() State { + s := NewVoidState("USDC", "0xUser") + s.HomeChannelID = &chanID + s.ID = GetStateID("0xUser", "USDC", 0, 0) + return *s + } + + cases := []struct { + name string + transitionType TransitionType + invalidAmounts []decimal.Decimal + errContains string + }{ + // Acknowledgement: amount must be exactly zero + { + "Acknowledgement", + TransitionTypeAcknowledgement, + []decimal.Decimal{decimal.NewFromInt(1), decimal.NewFromInt(-1)}, + "must be zero", + }, + // Finalize: amount must not be negative (zero and positive are allowed) + { + "Finalize", + TransitionTypeFinalize, + []decimal.Decimal{decimal.NewFromInt(-1)}, + "must not be negative", + }, + // All remaining transitions: amount must be strictly positive + {"HomeDeposit", TransitionTypeHomeDeposit, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"HomeWithdrawal", TransitionTypeHomeWithdrawal, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"TransferSend", TransitionTypeTransferSend, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"Commit", TransitionTypeCommit, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"MutualLock", TransitionTypeMutualLock, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"EscrowDeposit", TransitionTypeEscrowDeposit, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"EscrowLock", TransitionTypeEscrowLock, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"EscrowWithdraw", TransitionTypeEscrowWithdraw, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + {"Migrate", TransitionTypeMigrate, []decimal.Decimal{decimal.Zero, decimal.NewFromInt(-1)}, "must be positive"}, + } + + for _, tc := range cases { + for _, invalidAmount := range tc.invalidAmounts { + t.Run(tc.name+"/"+invalidAmount.String(), func(t *testing.T) { + t.Parallel() + + current := newCurrentState() + proposed := current.NextState() + proposed.Transition = *NewTransition(tc.transitionType, "0xTxID", "0xAccountID", invalidAmount) + proposed.UserSig = &sig + + err := advancer.ValidateAdvancement(current, *proposed) + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + }) + } + } +} + +// TestValidateAdvancement_RejectsOverflowDeposit ensures a home_deposit whose +// scaled amount exceeds uint256 is rejected before storage. Without the bound +// check, the offchain ledger would record the full amount while the signed +// payload truncates to the low 256 bits. +func TestValidateAdvancement_RejectsOverflowDeposit(t *testing.T) { + t.Parallel() + + store := newMockAssetStore() + store.AddToken(1, "0xToken", 0) // decimals=0 so amount maps 1:1 to scaled + advancer := NewStateAdvancerV1(store) + + userWallet := "0xUser" + asset := "USDC" + chanID := "0xHomeChannelId" + sig := "0xSig" + + current := NewVoidState(asset, userWallet) + current.HomeChannelID = &chanID + current.ID = GetStateID(userWallet, asset, 0, 0) + current.HomeLedger.TokenAddress = "0xToken" + current.HomeLedger.BlockchainID = 1 + + // scaled = 2^256, one above the uint256 range. + overflow := new(big.Int).Lsh(big.NewInt(1), 256) + amount := decimal.NewFromBigInt(overflow, 0) + + proposed := current.NextState() + _, err := proposed.ApplyHomeDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*current, *proposed) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid home ledger") + assert.Contains(t, err.Error(), "uint256") +} + +// TestValidateAdvancement_RejectsOverflowNetFlow checks that net-flow values +// exceeding the int256 range are rejected. Reached by combining balances that +// fit uint256 with a net flow that would overflow int256 (the signed type). +func TestValidateAdvancement_RejectsOverflowNetFlow(t *testing.T) { + t.Parallel() + + store := newMockAssetStore() + store.AddToken(1, "0xToken", 0) + advancer := NewStateAdvancerV1(store) + + userWallet := "0xUser" + asset := "USDC" + chanID := "0xHomeChannelId" + sig := "0xSig" + + current := NewVoidState(asset, userWallet) + current.HomeChannelID = &chanID + current.ID = GetStateID(userWallet, asset, 0, 0) + current.HomeLedger.TokenAddress = "0xToken" + current.HomeLedger.BlockchainID = 1 + + // 2^255, one above max int256 + overInt := new(big.Int).Lsh(big.NewInt(1), 255) + amount := decimal.NewFromBigInt(overInt, 0) + + proposed := current.NextState() + _, err := proposed.ApplyHomeDepositTransition(amount) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*current, *proposed) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid home ledger") + assert.Contains(t, err.Error(), "int256") +} + +// TestValidateAdvancement_RejectsOverflowEscrowLedger covers the escrow branch +// of the bound check: a state whose EscrowLedger already carries an out-of-range +// UserBalance is rejected even when the home ledger and the new transition are +// otherwise valid. +func TestValidateAdvancement_RejectsOverflowEscrowLedger(t *testing.T) { + t.Parallel() + + store := newMockAssetStore() + store.AddToken(1, "0xHomeToken", 0) + store.AddToken(2, "0xEscrowToken", 0) + advancer := NewStateAdvancerV1(store) + + userWallet := "0xUser" + asset := "USDC" + homeChanID := "0xHomeChannelId" + escrowChanID := "0xEscrowChannelId" + sig := "0xSig" + + // 2^256, one above the uint256 range. + overflow := new(big.Int).Lsh(big.NewInt(1), 256) + overflowDec := decimal.NewFromBigInt(overflow, 0) + + current := NewVoidState(asset, userWallet) + current.HomeChannelID = &homeChanID + current.EscrowChannelID = &escrowChanID + current.ID = GetStateID(userWallet, asset, 0, 0) + current.HomeLedger.TokenAddress = "0xHomeToken" + current.HomeLedger.BlockchainID = 1 + // Last transition is acknowledgement so NextState carries the escrow ledger + // forward and the new transition is not constrained. + current.Transition = *NewTransition(TransitionTypeAcknowledgement, "0x0", "0x0", decimal.Zero) + // Escrow balances sum to net flows so the equality check passes; the + // uint256 bound is what should reject the state. + current.EscrowLedger = &Ledger{ + BlockchainID: 2, + TokenAddress: "0xEscrowToken", + UserBalance: overflowDec, + UserNetFlow: overflowDec, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + } + + proposed := current.NextState() + _, err := proposed.ApplyHomeDepositTransition(decimal.NewFromInt(1)) + require.NoError(t, err) + proposed.UserSig = &sig + + err = advancer.ValidateAdvancement(*current, *proposed) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid escrow ledger") + assert.Contains(t, err.Error(), "uint256") +} diff --git a/pkg/core/state_packer.go b/pkg/core/state_packer.go index bd717043b..15e2a80e9 100644 --- a/pkg/core/state_packer.go +++ b/pkg/core/state_packer.go @@ -12,6 +12,72 @@ type StatePackerV1 struct { assetStore AssetStore } +// contractLedger mirrors the Solidity Ledger struct used inside the signed state +// payload. Allocation fields are encoded as uint256 and must fit [0, 2^256-1]; +// net-flow fields are encoded as int256 and must fit [-2^255, 2^255-1]. +type contractLedger struct { + ChainId uint64 + Token common.Address + Decimals uint8 + UserAllocation *big.Int + UserNetFlow *big.Int + NodeAllocation *big.Int + NodeNetFlow *big.Int +} + +// Validate guards against any caller that constructs a contractLedger without +// going through the bounds-checking decimal helpers, so values that would be +// silently truncated by ABI encoding are rejected before signing. +func (l contractLedger) Validate() error { + if err := checkUint256(l.UserAllocation); err != nil { + return fmt.Errorf("user allocation: %w", err) + } + if err := checkUint256(l.NodeAllocation); err != nil { + return fmt.Errorf("node allocation: %w", err) + } + if err := checkInt256(l.UserNetFlow); err != nil { + return fmt.Errorf("user net flow: %w", err) + } + if err := checkInt256(l.NodeNetFlow); err != nil { + return fmt.Errorf("node net flow: %w", err) + } + allocSum := new(big.Int).Add(l.UserAllocation, l.NodeAllocation) + if err := checkUint256(allocSum); err != nil { + return fmt.Errorf("allocation sum: %w", err) + } + netFlowSum := new(big.Int).Add(l.UserNetFlow, l.NodeNetFlow) + if err := checkInt256(netFlowSum); err != nil { + return fmt.Errorf("net flow sum: %w", err) + } + return nil +} + +func checkUint256(v *big.Int) error { + if v == nil { + return fmt.Errorf("value is nil") + } + if v.Sign() < 0 { + return fmt.Errorf("value %s is negative", v.String()) + } + if v.BitLen() > 256 { + return fmt.Errorf("value %s exceeds uint256 max (2^256-1)", v.String()) + } + return nil +} + +func checkInt256(v *big.Int) error { + if v == nil { + return fmt.Errorf("value is nil") + } + if v.Cmp(maxInt256) > 0 { + return fmt.Errorf("value %s exceeds int256 max (2^255-1)", v.String()) + } + if v.Cmp(minInt256) < 0 { + return fmt.Errorf("value %s below int256 min (-2^255)", v.String()) + } + return nil +} + func NewStatePackerV1(assetStore AssetStore) *StatePackerV1 { return &StatePackerV1{ assetStore: assetStore, @@ -62,36 +128,26 @@ func (p *StatePackerV1) packSigningData(state State) (common.Hash, []byte, error {Type: ledgerType}, // nonHomeState } - type contractLedger struct { - ChainId uint64 - Token common.Address - Decimals uint8 - UserAllocation *big.Int - UserNetFlow *big.Int - NodeAllocation *big.Int - NodeNetFlow *big.Int - } - homeDecimals, err := p.assetStore.GetTokenDecimals(state.HomeLedger.BlockchainID, state.HomeLedger.TokenAddress) if err != nil { return common.Hash{}, nil, err } - userBalanceBI, err := DecimalToBigInt(state.HomeLedger.UserBalance, homeDecimals) + userBalanceBI, err := DecimalToUint256(state.HomeLedger.UserBalance, homeDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("home user balance: %w", err) } - userNetFlowBI, err := DecimalToBigInt(state.HomeLedger.UserNetFlow, homeDecimals) + userNetFlowBI, err := DecimalToInt256(state.HomeLedger.UserNetFlow, homeDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("home user net flow: %w", err) } - nodeBalanceBI, err := DecimalToBigInt(state.HomeLedger.NodeBalance, homeDecimals) + nodeBalanceBI, err := DecimalToUint256(state.HomeLedger.NodeBalance, homeDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("home node balance: %w", err) } - nodeNetFlowBI, err := DecimalToBigInt(state.HomeLedger.NodeNetFlow, homeDecimals) + nodeNetFlowBI, err := DecimalToInt256(state.HomeLedger.NodeNetFlow, homeDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("home node net flow: %w", err) } homeLedger := contractLedger{ @@ -112,21 +168,21 @@ func (p *StatePackerV1) packSigningData(state State) (common.Hash, []byte, error return common.Hash{}, nil, err } - escrowUserBalanceBI, err := DecimalToBigInt(state.EscrowLedger.UserBalance, escrowDecimals) + escrowUserBalanceBI, err := DecimalToUint256(state.EscrowLedger.UserBalance, escrowDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("escrow user balance: %w", err) } - escrowUserNetFlowBI, err := DecimalToBigInt(state.EscrowLedger.UserNetFlow, escrowDecimals) + escrowUserNetFlowBI, err := DecimalToInt256(state.EscrowLedger.UserNetFlow, escrowDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("escrow user net flow: %w", err) } - escrowNodeBalanceBI, err := DecimalToBigInt(state.EscrowLedger.NodeBalance, escrowDecimals) + escrowNodeBalanceBI, err := DecimalToUint256(state.EscrowLedger.NodeBalance, escrowDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("escrow node balance: %w", err) } - escrowNodeNetFlowBI, err := DecimalToBigInt(state.EscrowLedger.NodeNetFlow, escrowDecimals) + escrowNodeNetFlowBI, err := DecimalToInt256(state.EscrowLedger.NodeNetFlow, escrowDecimals) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, fmt.Errorf("escrow node net flow: %w", err) } nonHomeLedger = contractLedger{ @@ -150,6 +206,13 @@ func (p *StatePackerV1) packSigningData(state State) (common.Hash, []byte, error } } + if err := homeLedger.Validate(); err != nil { + return common.Hash{}, nil, fmt.Errorf("invalid home ledger for signing: %w", err) + } + if err := nonHomeLedger.Validate(); err != nil { + return common.Hash{}, nil, fmt.Errorf("invalid escrow ledger for signing: %w", err) + } + intent := TransitionToIntent(state.Transition) signingData, err := signingDataArgs.Pack( diff --git a/pkg/core/state_packer_test.go b/pkg/core/state_packer_test.go index 6dbe79b71..72f7ba3b8 100644 --- a/pkg/core/state_packer_test.go +++ b/pkg/core/state_packer_test.go @@ -1,11 +1,13 @@ package core import ( + "math/big" "testing" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPackState(t *testing.T) { @@ -103,3 +105,177 @@ func TestPackState(t *testing.T) { assert.Equal(t, expectedPackedState, packedHex, "Packed state should match expected value") }) } + +// TestPackState_RejectsOutOfRangeValues guards against ABI silently truncating +// values to the low 256 bits. Each subtest sets a ledger field beyond its +// Solidity type range and expects PackState to error. +func TestPackState_RejectsOutOfRangeValues(t *testing.T) { + t.Parallel() + + channelID := "0x3e9dd25a843e3a234c278c6f3fab3983949e2404b276cacb3c47ada06e00f74b" + overflow256 := new(big.Int).Lsh(big.NewInt(1), 256) // 2^256 + overflow255 := new(big.Int).Lsh(big.NewInt(1), 255) // 2^255 + + homeToken := "0x90b7E285ab6cf4e3A2487669dba3E339dB8a3320" + escrowToken := "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + + newBaseState := func() State { + return State{ + Version: 1, + Asset: "test", + HomeChannelID: &channelID, + Transition: *NewTransition(TransitionTypeHomeDeposit, "tx", "acct", decimal.NewFromInt(1)), + HomeLedger: Ledger{ + BlockchainID: 42, + TokenAddress: homeToken, + UserBalance: decimal.Zero, + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + }, + } + } + + newEscrowLedger := func() *Ledger { + return &Ledger{ + BlockchainID: 4242, + TokenAddress: escrowToken, + UserBalance: decimal.Zero, + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.Zero, + } + } + + newStore := func() *mockAssetStore { + store := newMockAssetStore() + store.AddToken(42, homeToken, 0) + store.AddToken(4242, escrowToken, 0) + return store + } + + t.Run("user_balance_overflows_uint256", func(t *testing.T) { + t.Parallel() + + state := newBaseState() + state.HomeLedger.UserBalance = decimal.NewFromBigInt(overflow256, 0) + state.HomeLedger.UserNetFlow = decimal.NewFromBigInt(overflow256, 0) + + _, err := NewStatePackerV1(newStore()).PackState(state) + require.Error(t, err) + assert.Contains(t, err.Error(), "uint256") + }) + + t.Run("user_net_flow_overflows_int256", func(t *testing.T) { + t.Parallel() + + state := newBaseState() + // balance fits uint256 but net flow exceeds int256 + state.HomeLedger.UserBalance = decimal.NewFromBigInt(overflow255, 0) + state.HomeLedger.UserNetFlow = decimal.NewFromBigInt(overflow255, 0) + + _, err := NewStatePackerV1(newStore()).PackState(state) + require.Error(t, err) + assert.Contains(t, err.Error(), "int256") + }) + + t.Run("escrow_user_balance_overflows_uint256", func(t *testing.T) { + t.Parallel() + + state := newBaseState() + state.EscrowLedger = newEscrowLedger() + state.EscrowLedger.UserBalance = decimal.NewFromBigInt(overflow256, 0) + state.EscrowLedger.UserNetFlow = decimal.NewFromBigInt(overflow256, 0) + + _, err := NewStatePackerV1(newStore()).PackState(state) + require.Error(t, err) + assert.Contains(t, err.Error(), "uint256") + }) + + t.Run("escrow_user_net_flow_overflows_int256", func(t *testing.T) { + t.Parallel() + + state := newBaseState() + state.EscrowLedger = newEscrowLedger() + state.EscrowLedger.UserBalance = decimal.NewFromBigInt(overflow255, 0) + state.EscrowLedger.UserNetFlow = decimal.NewFromBigInt(overflow255, 0) + + _, err := NewStatePackerV1(newStore()).PackState(state) + require.Error(t, err) + assert.Contains(t, err.Error(), "int256") + }) +} + +// TestContractLedger_Validate covers the ABI-layer guard directly, bypassing +// Ledger.Validate. Solidity computes `userAllocation + nodeAllocation` as +// uint256 and `userNetFlow + nodeNetFlow` as int256; individually-valid scaled +// values can still overflow these aggregates, so the packer's last-chance +// check must reject them before signing. +func TestContractLedger_Validate(t *testing.T) { + t.Parallel() + + maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + + t.Run("happy_path", func(t *testing.T) { + t.Parallel() + l := contractLedger{ + UserAllocation: big.NewInt(10), + NodeAllocation: big.NewInt(20), + UserNetFlow: big.NewInt(10), + NodeNetFlow: big.NewInt(20), + } + assert.NoError(t, l.Validate()) + }) + + t.Run("net_flow_sum_overflows_int256", func(t *testing.T) { + t.Parallel() + l := contractLedger{ + UserAllocation: big.NewInt(0), + NodeAllocation: big.NewInt(0), + UserNetFlow: new(big.Int).Set(maxInt256), + NodeNetFlow: big.NewInt(1), + } + err := l.Validate() + require.Error(t, err) + assert.Contains(t, err.Error(), "net flow sum") + }) + + t.Run("net_flow_sum_underflows_int256", func(t *testing.T) { + t.Parallel() + l := contractLedger{ + UserAllocation: big.NewInt(0), + NodeAllocation: big.NewInt(0), + UserNetFlow: new(big.Int).Set(minInt256), + NodeNetFlow: big.NewInt(-1), + } + err := l.Validate() + require.Error(t, err) + assert.Contains(t, err.Error(), "net flow sum") + }) + + t.Run("allocation_sum_overflows_uint256", func(t *testing.T) { + t.Parallel() + l := contractLedger{ + UserAllocation: new(big.Int).Set(maxUint256), + NodeAllocation: big.NewInt(1), + UserNetFlow: big.NewInt(0), + NodeNetFlow: big.NewInt(0), + } + err := l.Validate() + require.Error(t, err) + assert.Contains(t, err.Error(), "allocation sum") + }) + + t.Run("net_flow_sum_at_boundary", func(t *testing.T) { + t.Parallel() + half := new(big.Int).Rsh(maxInt256, 1) + other := new(big.Int).Sub(maxInt256, half) + l := contractLedger{ + UserAllocation: big.NewInt(0), + NodeAllocation: big.NewInt(0), + UserNetFlow: half, + NodeNetFlow: other, + } + assert.NoError(t, l.Validate()) + }) +} diff --git a/pkg/core/types.go b/pkg/core/types.go index b1e815417..20e58ddbf 100644 --- a/pkg/core/types.go +++ b/pkg/core/types.go @@ -30,7 +30,8 @@ var ( ChannelStatusVoid ChannelStatus = 0 ChannelStatusOpen ChannelStatus = 1 ChannelStatusChallenged ChannelStatus = 2 - ChannelStatusClosed ChannelStatus = 3 + ChannelStatusClosing ChannelStatus = 3 // co-signed Finalize stored off-chain; on-chain close pending + ChannelStatusClosed ChannelStatus = 4 ) func (s ChannelStatus) String() string { @@ -41,6 +42,8 @@ func (s ChannelStatus) String() string { return "open" case ChannelStatusChallenged: return "challenged" + case ChannelStatusClosing: + return "closing" case ChannelStatusClosed: return "closed" default: @@ -81,6 +84,8 @@ func (s *ChannelStatus) scanString(v string) error { *s = ChannelStatusOpen case ChannelStatusChallenged.String(): *s = ChannelStatusChallenged + case ChannelStatusClosing.String(): + *s = ChannelStatusClosing case ChannelStatusClosed.String(): *s = ChannelStatusClosed default: @@ -170,6 +175,72 @@ func NewVoidState(asset, userWallet string) *State { } } +// NewChallengeRescueState constructs a ChallengeRescue state crediting amount to the +// user against closedChannelID. Placement depends on prev: +// +// - prev is in-channel (HomeChannelID != nil): the rescue opens a fresh epoch at +// (prev.Epoch+1, version=0) with a clean ledger seeded by amount. Used when no +// node-signed Finalize exists locally for the closed channel — the user's chain +// has not been advanced past prev yet. +// +// - prev is detached (HomeChannelID == nil): the rescue appends at (prev.Epoch, +// prev.Version+1), inheriting prev's ledger and adding amount on top. Used after +// a path-1 timeout close when a node-signed Finalize is on file: the sign-time +// NextState() has already advanced the user to a fresh epoch and post-Finalize +// receiver credits may already live there. Placing rescue at v=0 would collide +// on deterministic state ID; appending after the detached tip avoids that. +// +// closedChannelID is the on-chain channel whose close triggers the rescue. It is +// recorded as the rescue's transition AccountID so the credit is traceable back to +// the settlement. +func NewChallengeRescueState(prev State, closedChannelID string, amount decimal.Decimal) (*State, error) { + if closedChannelID == "" { + return nil, fmt.Errorf("closed channel ID is empty") + } + if amount.IsNegative() { + return nil, fmt.Errorf("challenge rescue amount must be non-negative") + } + + var rescue *State + if prev.HomeChannelID == nil { + // Detached tip: append at (prev.Epoch, prev.Version+1), carry ledger forward. + rescue = &State{ + Asset: prev.Asset, + UserWallet: prev.UserWallet, + Epoch: prev.Epoch, + Version: prev.Version + 1, + HomeLedger: Ledger{ + UserBalance: prev.HomeLedger.UserBalance.Add(amount), + UserNetFlow: prev.HomeLedger.UserNetFlow, + NodeBalance: prev.HomeLedger.NodeBalance, + NodeNetFlow: prev.HomeLedger.NodeNetFlow.Add(amount), + }, + } + } else { + // In-channel prev: wrap to a fresh epoch with a clean ledger seeded by amount. + rescue = &State{ + Asset: prev.Asset, + UserWallet: prev.UserWallet, + Epoch: prev.Epoch + 1, + Version: 0, + HomeLedger: Ledger{ + UserBalance: amount, + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: amount, + }, + } + } + rescue.ID = GetStateID(rescue.UserWallet, rescue.Asset, rescue.Epoch, rescue.Version) + + txID, err := GetReceiverTransactionID(closedChannelID, rescue.ID) + if err != nil { + return nil, err + } + rescue.Transition = *NewTransition(TransitionTypeChallengeRescue, txID, closedChannelID, amount) + return rescue, nil +} + func (state State) NextState() *State { var nextState *State if state.IsFinal() { @@ -248,6 +319,15 @@ func (state *State) IsFinal() bool { return state.Transition.Type == TransitionTypeFinalize } +// validateTransitionInputs checks that the state has no active transition yet, +// and may be extended in the future to validate other common preconditions for applying transitions. +func (state *State) validateTransitionInputs() error { + if state.Transition.Type != TransitionTypeVoid { + return fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + } + return nil +} + func (state *State) ApplyAcknowledgementTransition() (Transition, error) { if state.Transition.Type != TransitionTypeVoid { return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) @@ -262,8 +342,8 @@ func (state *State) ApplyAcknowledgementTransition() (Transition, error) { } func (state *State) ApplyHomeDepositTransition(amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } if state.HomeChannelID == nil { return Transition{}, fmt.Errorf("missing home channel ID") @@ -284,8 +364,8 @@ func (state *State) ApplyHomeDepositTransition(amount decimal.Decimal) (Transiti } func (state *State) ApplyHomeWithdrawalTransition(amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } if state.HomeChannelID == nil { return Transition{}, fmt.Errorf("missing home channel ID") @@ -306,8 +386,8 @@ func (state *State) ApplyHomeWithdrawalTransition(amount decimal.Decimal) (Trans } func (state *State) ApplyTransferSendTransition(recipient string, amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } // TODO: maybe validate that recipient is a correct UserWallet format accountID := recipient @@ -325,8 +405,8 @@ func (state *State) ApplyTransferSendTransition(recipient string, amount decimal } func (state *State) ApplyTransferReceiveTransition(sender string, amount decimal.Decimal, txID string) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } // TODO: maybe validate that recipient is a correct UserWallet format accountID := sender @@ -339,8 +419,8 @@ func (state *State) ApplyTransferReceiveTransition(sender string, amount decimal } func (state *State) ApplyCommitTransition(accountID string, amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } // TODO: maybe validate that AccountID has correct AppSessionID format txID, err := GetSenderTransactionID(accountID, state.ID) @@ -357,8 +437,8 @@ func (state *State) ApplyCommitTransition(accountID string, amount decimal.Decim } func (state *State) ApplyReleaseTransition(accountID string, amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } // TODO: maybe validate that recipient is a correct UserWallet format txID, err := GetReceiverTransactionID(accountID, state.ID) @@ -374,8 +454,8 @@ func (state *State) ApplyReleaseTransition(accountID string, amount decimal.Deci } func (state *State) ApplyMutualLockTransition(blockchainID uint64, tokenAddress string, amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } if state.HomeChannelID == nil { return Transition{}, fmt.Errorf("missing home channel ID") @@ -418,8 +498,8 @@ func (state *State) ApplyMutualLockTransition(blockchainID uint64, tokenAddress } func (state *State) ApplyEscrowDepositTransition(amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } if state.EscrowChannelID == nil { return Transition{}, fmt.Errorf("internal error: escrow channel ID is nil") @@ -438,7 +518,7 @@ func (state *State) ApplyEscrowDepositTransition(amount decimal.Decimal) (Transi state.Transition = *newTransition state.HomeLedger.UserBalance = state.HomeLedger.UserBalance.Add(newTransition.Amount) - state.HomeLedger.NodeNetFlow = state.HomeLedger.NodeNetFlow.Add(newTransition.Amount) + state.HomeLedger.NodeBalance = decimal.Zero state.EscrowLedger.UserBalance = state.EscrowLedger.UserBalance.Sub(newTransition.Amount) state.EscrowLedger.NodeNetFlow = state.EscrowLedger.NodeNetFlow.Sub(newTransition.Amount) @@ -447,8 +527,8 @@ func (state *State) ApplyEscrowDepositTransition(amount decimal.Decimal) (Transi } func (state *State) ApplyEscrowLockTransition(blockchainID uint64, tokenAddress string, amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } if state.HomeChannelID == nil { return Transition{}, fmt.Errorf("missing home channel ID") @@ -459,6 +539,9 @@ func (state *State) ApplyEscrowLockTransition(blockchainID uint64, tokenAddress if tokenAddress == "" { return Transition{}, fmt.Errorf("invalid token address") } + if state.HomeLedger.UserBalance.LessThan(amount) { + return Transition{}, fmt.Errorf("insufficient user balance for escrow lock") + } escrowChannelID, err := GetEscrowChannelID(*state.HomeChannelID, state.Version) if err != nil { @@ -475,6 +558,8 @@ func (state *State) ApplyEscrowLockTransition(blockchainID uint64, tokenAddress newTransition := NewTransition(TransitionTypeEscrowLock, txID, accountID, amount) state.Transition = *newTransition + state.HomeLedger.NodeBalance = decimal.Zero + state.EscrowLedger = &Ledger{ BlockchainID: blockchainID, TokenAddress: tokenAddress, @@ -488,8 +573,8 @@ func (state *State) ApplyEscrowLockTransition(blockchainID uint64, tokenAddress } func (state *State) ApplyEscrowWithdrawTransition(amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } if state.EscrowChannelID == nil { return Transition{}, fmt.Errorf("internal error: escrow channel ID is nil") @@ -517,12 +602,14 @@ func (state *State) ApplyEscrowWithdrawTransition(amount decimal.Decimal) (Trans } func (state *State) ApplyMigrateTransition(amount decimal.Decimal) (Transition, error) { - if state.Transition.Type != TransitionTypeVoid { - return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) + if err := state.validateTransitionInputs(); err != nil { + return Transition{}, err } return Transition{}, fmt.Errorf("migrate transition not implemented yet") } +// This transition can also contain non-zero amount in case previous state had a non-zero user balance. +// Basically, amount in this transition means that user is withdrawing it from the channel as part of finalization. func (state *State) ApplyFinalizeTransition() (Transition, error) { if state.Transition.Type != TransitionTypeVoid { return Transition{}, fmt.Errorf("state already has a transition: %s", state.Transition.Type.String()) @@ -579,7 +666,12 @@ func (l1 Ledger) Equal(l2 Ledger) error { return nil } -func (l Ledger) Validate() error { +// Validate checks ledger invariants and ensures all four scaled values fit the +// Solidity ABI ranges used for signing and onchain calls. Balances must fit +// uint256 ([0, 2^256-1]); net flows must fit int256 ([-2^255, 2^255-1]). +// assetDecimals is the token's decimal exponent, used to scale decimal values +// to their smallest onchain units before the range check. +func (l Ledger) Validate(assetDecimals uint8) error { if l.TokenAddress == "" { return fmt.Errorf("invalid token address") } @@ -598,6 +690,30 @@ func (l Ledger) Validate() error { return fmt.Errorf("ledger balances do not match net flows: balances=%s, net_flows=%s", sumBalances.String(), sumNetFlows.String()) } + if _, err := DecimalToUint256(l.UserBalance, assetDecimals); err != nil { + return fmt.Errorf("user balance out of uint256 range: %w", err) + } + if _, err := DecimalToUint256(l.NodeBalance, assetDecimals); err != nil { + return fmt.Errorf("node balance out of uint256 range: %w", err) + } + if _, err := DecimalToInt256(l.UserNetFlow, assetDecimals); err != nil { + return fmt.Errorf("user net flow out of int256 range: %w", err) + } + if _, err := DecimalToInt256(l.NodeNetFlow, assetDecimals); err != nil { + return fmt.Errorf("node net flow out of int256 range: %w", err) + } + + // Solidity computes `userAllocation + nodeAllocation` as uint256 and + // `userNetFlow + nodeNetFlow` as int256. Individually-valid scaled values + // can still overflow these aggregates, producing a state the contract will + // reject. + if _, err := DecimalToUint256(sumBalances, assetDecimals); err != nil { + return fmt.Errorf("allocation sum out of uint256 range: %w", err) + } + if _, err := DecimalToInt256(sumNetFlows, assetDecimals); err != nil { + return fmt.Errorf("net flow sum out of int256 range: %w", err) + } + return nil } @@ -622,6 +738,10 @@ const ( TransactionTypeMutualLock TransactionType = 120 TransactionTypeFinalize = 200 + + // TransactionTypeChallengeRescue mirrors TransitionTypeChallengeRescue and records + // the squashed credit produced when a challenged home channel is closed onchain. + TransactionTypeChallengeRescue TransactionType = 201 ) // String returns the human-readable name of the transaction type @@ -651,6 +771,8 @@ func (t TransactionType) String() string { return "rebalance" case TransactionTypeFinalize: return "finalize" + case TransactionTypeChallengeRescue: + return "challenge_rescue" default: return "unknown" } @@ -690,8 +812,8 @@ func NewTransactionFromTransition(senderState *State, receiverState *State, tran var toAccount, fromAccount string // Transition validator is expected to make sure that all the fields are present and valid. - if transition.Type != TransitionTypeRelease && senderState == nil { - return nil, fmt.Errorf("sender state must not be nil for non-release transitions") + if transition.Type != TransitionTypeRelease && transition.Type != TransitionTypeChallengeRescue && senderState == nil { + return nil, fmt.Errorf("sender state must not be nil for non-release / non-challenge-rescue transitions") } var senderStateID *string @@ -794,6 +916,13 @@ func NewTransactionFromTransition(senderState *State, receiverState *State, tran txType = TransactionTypeFinalize fromAccount = senderState.UserWallet toAccount = *senderState.HomeChannelID + case TransitionTypeChallengeRescue: + if receiverState == nil { + return nil, fmt.Errorf("receiver state must not be nil for 'challenge_rescue' transition") + } + txType = TransactionTypeChallengeRescue + fromAccount = transition.AccountID + toAccount = receiverState.UserWallet default: return nil, fmt.Errorf("invalid transition type") } @@ -847,8 +976,35 @@ const ( TransitionTypeMutualLock TransitionType = 120 // AccountID: EscrowChannelID TransitionTypeFinalize TransitionType = 200 // AccountID: HomeChannelID + + // TransitionTypeChallengeRescue is issued by the node when a challenged home channel + // is closed onchain with a non-finalize transition. It squashes the sum of receiver + // states accrued under the no-sign-while-challenged rule into a single credit on the + // user's ledger, with AccountID set to the closed channel ID and HomeChannelID nil. + // Only the node can produce this transition; it has no state-advancer validation rule. + TransitionTypeChallengeRescue TransitionType = 201 // AccountID: closed HomeChannelID ) +// AllTransitionTypes enumerates every defined transition. Kept beside the const +// block so adding a new transition here is the natural place to update consumers +// that iterate the full domain (metrics seeding, drift tests). +var AllTransitionTypes = []TransitionType{ + TransitionTypeVoid, + TransitionTypeAcknowledgement, + TransitionTypeHomeDeposit, + TransitionTypeHomeWithdrawal, + TransitionTypeEscrowDeposit, + TransitionTypeEscrowWithdraw, + TransitionTypeTransferSend, + TransitionTypeTransferReceive, + TransitionTypeCommit, + TransitionTypeRelease, + TransitionTypeMigrate, + TransitionTypeEscrowLock, + TransitionTypeMutualLock, + TransitionTypeFinalize, +} + // String returns the human-readable name of the transition type func (t TransitionType) String() string { switch t { @@ -880,6 +1036,8 @@ func (t TransitionType) String() string { return "migrate" case TransitionTypeFinalize: return "finalize" + case TransitionTypeChallengeRescue: + return "challenge_rescue" default: return "unknown" } @@ -1045,6 +1203,7 @@ type EscrowWithdrawalDataResponse struct { type BalanceEntry struct { Asset string `json:"asset"` // Asset symbol Balance decimal.Decimal `json:"balance"` // Balance amount + Enforced decimal.Decimal `json:"enforced"` // On-chain enforced balance } // PaginationParams provides pagination configuration for getters @@ -1079,13 +1238,13 @@ type PaginationMetadata struct { PageCount uint32 `json:"page_count"` // Total number of pages } -// NodeConfig represents the configuration of a Clearnode instance. +// NodeConfig represents the configuration of a Nitronode instance. // It includes the node's identity, version, and supported blockchain networks. type NodeConfig struct { - // NodeAddress is the Ethereum address of the clearnode operator + // NodeAddress is the Ethereum address of the nitronode operator NodeAddress string - // NodeVersion is the software version of the clearnode instance + // NodeVersion is the software version of the nitronode instance NodeVersion string // SupportedSigValidators is the list of supported signature validator types diff --git a/pkg/core/types_test.go b/pkg/core/types_test.go index b9b66cb79..304fffccb 100644 --- a/pkg/core/types_test.go +++ b/pkg/core/types_test.go @@ -1,6 +1,7 @@ package core import ( + "math/big" "testing" "github.com/shopspring/decimal" @@ -194,6 +195,114 @@ func TestState_ApplyReleaseTransition(t *testing.T) { assert.Equal(t, "10", state.HomeLedger.UserBalance.String()) } +func TestNewChallengeRescueState(t *testing.T) { + t.Parallel() + + closedChannelID := "0xClosedChannel" + makePrev := func() State { + channelID := closedChannelID + return State{ + Asset: "USDC", + UserWallet: "0xUser", + Epoch: 3, + Version: 7, + HomeChannelID: &channelID, + HomeLedger: Ledger{ + TokenAddress: "0xToken", + BlockchainID: 1, + UserBalance: decimal.NewFromInt(50), + UserNetFlow: decimal.NewFromInt(50), + }, + } + } + + t.Run("Success - in-channel prev wraps to fresh epoch at version 0", func(t *testing.T) { + prev := makePrev() + amount := decimal.NewFromInt(42) + + rescue, err := NewChallengeRescueState(prev, closedChannelID, amount) + require.NoError(t, err) + require.NotNil(t, rescue) + + assert.Equal(t, prev.Asset, rescue.Asset) + assert.Equal(t, prev.UserWallet, rescue.UserWallet) + assert.Equal(t, prev.Epoch+1, rescue.Epoch) + assert.Equal(t, uint64(0), rescue.Version) + assert.Nil(t, rescue.HomeChannelID) + assert.Empty(t, rescue.HomeLedger.TokenAddress) + assert.Equal(t, uint64(0), rescue.HomeLedger.BlockchainID) + assert.True(t, amount.Equal(rescue.HomeLedger.UserBalance)) + assert.True(t, amount.Equal(rescue.HomeLedger.NodeNetFlow)) + assert.True(t, rescue.HomeLedger.UserNetFlow.IsZero()) + assert.True(t, rescue.HomeLedger.NodeBalance.IsZero()) + + assert.Equal(t, TransitionTypeChallengeRescue, rescue.Transition.Type) + assert.Equal(t, closedChannelID, rescue.Transition.AccountID) + assert.True(t, amount.Equal(rescue.Transition.Amount)) + + expectedTxID, err := GetReceiverTransactionID(closedChannelID, rescue.ID) + require.NoError(t, err) + assert.Equal(t, expectedTxID, rescue.Transition.TxID) + }) + + t.Run("Success - detached prev appends at next version inheriting ledger", func(t *testing.T) { + // Simulates path-1 timeout after a signed Finalize: user's chain already advanced + // via NextState() to a fresh epoch with post-Finalize receiver credits at v=0..M. + prev := State{ + Asset: "USDC", + UserWallet: "0xUser", + Epoch: 4, + Version: 2, + HomeChannelID: nil, + HomeLedger: Ledger{ + UserBalance: decimal.NewFromInt(10), + UserNetFlow: decimal.Zero, + NodeBalance: decimal.Zero, + NodeNetFlow: decimal.NewFromInt(10), + }, + } + amount := decimal.NewFromInt(42) + + rescue, err := NewChallengeRescueState(prev, closedChannelID, amount) + require.NoError(t, err) + require.NotNil(t, rescue) + + assert.Equal(t, prev.Epoch, rescue.Epoch) + assert.Equal(t, prev.Version+1, rescue.Version) + assert.Nil(t, rescue.HomeChannelID) + assert.True(t, decimal.NewFromInt(52).Equal(rescue.HomeLedger.UserBalance), "got %s", rescue.HomeLedger.UserBalance.String()) + assert.True(t, decimal.NewFromInt(52).Equal(rescue.HomeLedger.NodeNetFlow), "got %s", rescue.HomeLedger.NodeNetFlow.String()) + assert.True(t, rescue.HomeLedger.UserNetFlow.IsZero()) + assert.True(t, rescue.HomeLedger.NodeBalance.IsZero()) + + assert.Equal(t, TransitionTypeChallengeRescue, rescue.Transition.Type) + assert.Equal(t, closedChannelID, rescue.Transition.AccountID) + assert.True(t, amount.Equal(rescue.Transition.Amount)) + }) + + t.Run("Accepts zero amount", func(t *testing.T) { + prev := makePrev() + rescue, err := NewChallengeRescueState(prev, closedChannelID, decimal.Zero) + require.NoError(t, err) + require.NotNil(t, rescue) + assert.True(t, rescue.Transition.Amount.IsZero()) + assert.True(t, rescue.HomeLedger.UserBalance.IsZero()) + assert.True(t, rescue.HomeLedger.NodeNetFlow.IsZero()) + }) + + t.Run("Rejects negative amount", func(t *testing.T) { + prev := makePrev() + _, err := NewChallengeRescueState(prev, closedChannelID, decimal.NewFromInt(-1)) + require.Error(t, err) + }) + + t.Run("Rejects empty closed channel ID", func(t *testing.T) { + prev := makePrev() + _, err := NewChallengeRescueState(prev, "", decimal.NewFromInt(1)) + require.Error(t, err) + }) +} + func TestState_ApplyMutualLockTransition(t *testing.T) { t.Parallel() state := NewVoidState("USDC", "0xUser") @@ -221,34 +330,99 @@ func TestState_ApplyMutualLockTransition(t *testing.T) { func TestState_ApplyEscrowDepositTransition(t *testing.T) { t.Parallel() - state := NewVoidState("USDC", "0xUser") - state.ID = "0xStateID" - - escrowID := "0xEscrow" - state.EscrowChannelID = &escrowID - state.EscrowLedger = &Ledger{UserBalance: decimal.NewFromInt(100)} - amount := decimal.NewFromInt(10) - transition, err := state.ApplyEscrowDepositTransition(amount) - require.NoError(t, err) - assert.Equal(t, TransitionTypeEscrowDeposit, transition.Type) - assert.Equal(t, "10", state.HomeLedger.UserBalance.String()) - assert.Equal(t, "90", state.EscrowLedger.UserBalance.String()) + t.Run("user_balance_increases_escrow_balance_decreases", func(t *testing.T) { + t.Parallel() + state := NewVoidState("USDC", "0xUser") + state.ID = "0xStateID" + + escrowID := "0xEscrow" + state.EscrowChannelID = &escrowID + state.EscrowLedger = &Ledger{UserBalance: decimal.NewFromInt(100)} + + amount := decimal.NewFromInt(10) + transition, err := state.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + assert.Equal(t, TransitionTypeEscrowDeposit, transition.Type) + assert.Equal(t, "10", state.HomeLedger.UserBalance.String()) + assert.Equal(t, "90", state.EscrowLedger.UserBalance.String()) + }) + + t.Run("clears_node_balance_and_leaves_net_flows_unchanged", func(t *testing.T) { + t.Parallel() + state := NewVoidState("USDC", "0xUser") + state.ID = "0xStateID" + + escrowID := "0xEscrow" + state.EscrowChannelID = &escrowID + + // Realistic precondition: state after ApplyMutualLockTransition where the + // node has locked funds on the home chain. + state.HomeLedger.NodeBalance = decimal.NewFromInt(10) + state.HomeLedger.NodeNetFlow = decimal.NewFromInt(10) + state.EscrowLedger = &Ledger{ + UserBalance: decimal.NewFromInt(10), + UserNetFlow: decimal.NewFromInt(10), + } + + prevNodeNetFlow := state.HomeLedger.NodeNetFlow + prevUserNetFlow := state.HomeLedger.UserNetFlow + + amount := decimal.NewFromInt(10) + _, err := state.ApplyEscrowDepositTransition(amount) + require.NoError(t, err) + + // User receives funds on the home chain. + assert.Equal(t, "10", state.HomeLedger.UserBalance.String()) + + // Node's locked allocation must be cleared (on-chain: nodeAllocation == 0). + assert.Equal(t, "0", state.HomeLedger.NodeBalance.String()) + + // Net flows must not change (on-chain: nodeNfDelta == 0, userNfDelta == 0). + assert.True(t, state.HomeLedger.NodeNetFlow.Equal(prevNodeNetFlow), + "NodeNetFlow must remain unchanged: got %s, want %s", + state.HomeLedger.NodeNetFlow.String(), prevNodeNetFlow.String()) + assert.True(t, state.HomeLedger.UserNetFlow.Equal(prevUserNetFlow), + "UserNetFlow must remain unchanged: got %s, want %s", + state.HomeLedger.UserNetFlow.String(), prevUserNetFlow.String()) + }) } func TestState_ApplyEscrowLockTransition(t *testing.T) { t.Parallel() - state := NewVoidState("USDC", "0xUser") - chanID := "0xChan" - state.HomeChannelID = &chanID - state.ID = "0xStateID" - amount := decimal.NewFromInt(10) - transition, err := state.ApplyEscrowLockTransition(2, "0xT", amount) - require.NoError(t, err) - assert.Equal(t, TransitionTypeEscrowLock, transition.Type) - assert.NotNil(t, state.EscrowLedger) - assert.Equal(t, "10", state.EscrowLedger.NodeBalance.String()) + t.Run("success_creates_escrow_ledger_and_clears_node_balance", func(t *testing.T) { + t.Parallel() + state := NewVoidState("USDC", "0xUser") + chanID := "0xChan" + state.HomeChannelID = &chanID + state.ID = "0xStateID" + state.HomeLedger.UserBalance = decimal.NewFromInt(50) + state.HomeLedger.NodeBalance = decimal.NewFromInt(5) + + amount := decimal.NewFromInt(10) + transition, err := state.ApplyEscrowLockTransition(2, "0xT", amount) + require.NoError(t, err) + assert.Equal(t, TransitionTypeEscrowLock, transition.Type) + assert.NotNil(t, state.EscrowLedger) + assert.Equal(t, "10", state.EscrowLedger.NodeBalance.String()) + assert.True(t, state.HomeLedger.NodeBalance.IsZero(), + "home NodeBalance must be cleared to zero, got %s", state.HomeLedger.NodeBalance.String()) + }) + + t.Run("reject_insufficient_user_balance", func(t *testing.T) { + t.Parallel() + state := NewVoidState("USDC", "0xUser") + chanID := "0xChan" + state.HomeChannelID = &chanID + state.ID = "0xStateID" + state.HomeLedger.UserBalance = decimal.NewFromInt(5) + + amount := decimal.NewFromInt(10) + _, err := state.ApplyEscrowLockTransition(2, "0xT", amount) + require.Error(t, err) + assert.Contains(t, err.Error(), "insufficient user balance for escrow lock") + }) } func TestState_ApplyEscrowWithdrawTransition(t *testing.T) { @@ -305,26 +479,54 @@ func TestLedger_Equal_Validate(t *testing.T) { } l2 := l1 assert.NoError(t, l1.Equal(l2)) - assert.NoError(t, l1.Validate()) + assert.NoError(t, l1.Validate(6)) l2.TokenAddress = "0xOther" assert.Error(t, l1.Equal(l2)) lInvalid := l1 lInvalid.TokenAddress = "" - assert.Error(t, lInvalid.Validate()) + assert.Error(t, lInvalid.Validate(6)) lInvalid = l1 lInvalid.BlockchainID = 0 - assert.Error(t, lInvalid.Validate()) + assert.Error(t, lInvalid.Validate(6)) lInvalid = l1 lInvalid.UserBalance = decimal.NewFromInt(-1) - assert.Error(t, lInvalid.Validate()) + assert.Error(t, lInvalid.Validate(6)) lInvalid = l1 lInvalid.UserNetFlow = decimal.NewFromInt(999) // Mismatch - assert.Error(t, lInvalid.Validate()) + assert.Error(t, lInvalid.Validate(6)) + + // Each net-flow field fits int256, but their sum does not. Solidity computes + // `userNetFlow + nodeNetFlow` as int256, so an unchecked sum overflow would + // produce a state the contract panics on. + lSumOverflow := Ledger{ + TokenAddress: "0xT", + BlockchainID: 1, + UserBalance: decimal.NewFromBigInt(maxInt256, 0), + NodeBalance: decimal.NewFromInt(1), + UserNetFlow: decimal.NewFromBigInt(maxInt256, 0), + NodeNetFlow: decimal.NewFromInt(1), + } + err := lSumOverflow.Validate(0) + require.Error(t, err) + assert.Contains(t, err.Error(), "net flow sum out of int256 range") + + // Sum exactly at the int256 boundary must pass. + halfMax := new(big.Int).Rsh(maxInt256, 1) // (2^255-1)/2 + otherHalf := new(big.Int).Sub(maxInt256, halfMax) + lBoundary := Ledger{ + TokenAddress: "0xT", + BlockchainID: 1, + UserBalance: decimal.NewFromBigInt(halfMax, 0), + NodeBalance: decimal.NewFromBigInt(otherHalf, 0), + UserNetFlow: decimal.NewFromBigInt(halfMax, 0), + NodeNetFlow: decimal.NewFromBigInt(otherHalf, 0), + } + assert.NoError(t, lBoundary.Validate(0)) } func TestTransactionType_String(t *testing.T) { diff --git a/pkg/core/utils.go b/pkg/core/utils.go index b43c6b4ff..77c2987ea 100644 --- a/pkg/core/utils.go +++ b/pkg/core/utils.go @@ -7,15 +7,27 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + ethmath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/shopspring/decimal" ) +// maxInt256 = 2^255 - 1, the largest value representable as Solidity int256. +var maxInt256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(1)) + +// minInt256 = -2^255, the smallest value representable as Solidity int256. +var minInt256 = new(big.Int).Neg(new(big.Int).Lsh(big.NewInt(1), 255)) + const ( // ChannelHubVersion is the version of the ChannelHub contract that this code is compatible with. // This version is encoded as the first byte of the channelId to prevent replay attacks // across different ChannelHub deployments on the same chain. ChannelHubVersion uint8 = 1 + + // ChannelMinChallengeDuration and ChannelMaxChallengeDuration mirror the + // ChannelHub challenge-duration bounds. + ChannelMinChallengeDuration uint32 = 24 * 60 * 60 + ChannelMaxChallengeDuration uint32 = 7 * 24 * 60 * 60 ) var ( @@ -25,6 +37,23 @@ var ( uint256Type, _ = abi.NewType("uint256", "", nil) ) +func NormalizeHexAddress(s string) (string, error) { + s = strings.ToLower(s) + + if !strings.HasPrefix(s, "0x") || len(s) != 42 { + return "", fmt.Errorf("invalid hex address: incorrect length, expected 42 characters including 0x prefix") + } + + for i := 2; i < len(s); i++ { + c := s[i] + if !(('0' <= c && c <= '9') || ('a' <= c && c <= 'f')) { + return "", fmt.Errorf("invalid hex address: character '%c' at position %d is not a valid hexadecimal character", c, i) + } + } + + return s, nil +} + func TransitionToIntent(transition Transition) uint8 { switch transition.Type { case TransitionTypeTransferSend, @@ -63,24 +92,56 @@ func ValidateDecimalPrecision(amount decimal.Decimal, maxDecimals uint8) error { return nil } -// DecimalToBigInt converts a decimal.Decimal amount to *big.Int scaled to the token's smallest unit. -// For example, 1.23 USDC (6 decimals) becomes 1230000. -// This is used when preparing amounts for smart contract calls. -func DecimalToBigInt(amount decimal.Decimal, decimals uint8) (*big.Int, error) { - // 1. Calculate the multiplier (e.g., 10^6) +// decimalToBigInt scales a decimal amount to the token's smallest unit and +// returns an unbounded *big.Int. Internal helper for DecimalToUint256 / +// DecimalToInt256; callers outside this file must use the bounded variants so +// values exceeding the Solidity ABI range are rejected rather than silently +// truncated. +func decimalToBigInt(amount decimal.Decimal, decimals uint8) (*big.Int, error) { multiplier := decimal.New(1, int32(decimals)) - - // 2. Scale the amount scaled := amount.Mul(multiplier) if !scaled.IsInteger() { return nil, fmt.Errorf("amount %s exceeds maximum decimal precision: max %d decimals allowed", amount.String(), decimals) } - // 4. Safe to convert return scaled.BigInt(), nil } +// DecimalToUint256 scales amount to the token's smallest unit and rejects values +// outside the Solidity uint256 range [0, 2^256 - 1]. Use for allocation/balance +// fields that are ABI-encoded as uint256 prior to signing or onchain submission. +func DecimalToUint256(amount decimal.Decimal, decimals uint8) (*big.Int, error) { + scaled, err := decimalToBigInt(amount, decimals) + if err != nil { + return nil, err + } + if scaled.Sign() < 0 { + return nil, fmt.Errorf("amount %s is negative, expected uint256 range [0, 2^256-1]", amount.String()) + } + if scaled.Cmp(ethmath.MaxBig256) > 0 { + return nil, fmt.Errorf("amount %s exceeds uint256 max (2^256-1)", amount.String()) + } + return scaled, nil +} + +// DecimalToInt256 scales amount to the token's smallest unit and rejects values +// outside the Solidity int256 range [-2^255, 2^255 - 1]. Use for net-flow fields +// that are ABI-encoded as int256 prior to signing or onchain submission. +func DecimalToInt256(amount decimal.Decimal, decimals uint8) (*big.Int, error) { + scaled, err := decimalToBigInt(amount, decimals) + if err != nil { + return nil, err + } + if scaled.Cmp(maxInt256) > 0 { + return nil, fmt.Errorf("amount %s exceeds int256 max (2^255-1)", amount.String()) + } + if scaled.Cmp(minInt256) < 0 { + return nil, fmt.Errorf("amount %s below int256 min (-2^255)", amount.String()) + } + return scaled, nil +} + // getHomeChannelID is the internal implementation that generates a unique identifier for a primary channel // based on its definition and version. This matches the Solidity getChannelId function which computes // keccak256(abi.encode(ChannelDefinition)) and then sets the first byte to the version. diff --git a/pkg/core/utils_test.go b/pkg/core/utils_test.go index 6fb67c9c7..6b2a67929 100644 --- a/pkg/core/utils_test.go +++ b/pkg/core/utils_test.go @@ -10,6 +10,80 @@ import ( "github.com/stretchr/testify/require" ) +func TestNormalizeHexAddress(t *testing.T) { + t.Parallel() + tests := []struct { + name string + input string + want string + wantErr bool + }{ + { + name: "valid_lowercase", + input: "0x1234567890abcdef1234567890abcdef12345678", + want: "0x1234567890abcdef1234567890abcdef12345678", + }, + { + name: "valid_uppercase_normalized", + input: "0xABCDEF1234567890ABCDEF1234567890ABCDEF12", + want: "0xabcdef1234567890abcdef1234567890abcdef12", + }, + { + name: "valid_mixed_case", + input: "0xAbCdEf1234567890aBcDeF1234567890AbCdEf12", + want: "0xabcdef1234567890abcdef1234567890abcdef12", + }, + { + name: "valid_checksum_address", + input: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + want: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", + }, + { + name: "missing_0x_prefix", + input: "1234567890abcdef1234567890abcdef12345678", + wantErr: true, + }, + { + name: "too_short", + input: "0x1234", + wantErr: true, + }, + { + name: "too_long", + input: "0x1234567890abcdef1234567890abcdef1234567890", + wantErr: true, + }, + { + name: "empty_string", + input: "", + wantErr: true, + }, + { + name: "invalid_hex_char", + input: "0x1234567890abcdef1234567890abcdef1234567g", + wantErr: true, + }, + { + name: "all_zeros", + input: "0x0000000000000000000000000000000000000000", + want: "0x0000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := NormalizeHexAddress(tt.input) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + func TestValidateDecimalPrecision(t *testing.T) { t.Parallel() tests := []struct { @@ -296,7 +370,7 @@ func TestDecimalToBigInt(t *testing.T) { amount, err := decimal.NewFromString(tt.amount) assert.NoError(t, err, "Test setup error: invalid amount string") - result, err := DecimalToBigInt(amount, tt.decimals) + result, err := decimalToBigInt(amount, tt.decimals) assert.NoError(t, err) expected, ok := new(big.Int).SetString(tt.expected, 10) @@ -312,7 +386,7 @@ func TestDecimalToBigInt_NegativeAmounts(t *testing.T) { t.Run("negative_usdc", func(t *testing.T) { t.Parallel() amount := decimal.NewFromFloat(-1.23) - result, err := DecimalToBigInt(amount, 6) + result, err := decimalToBigInt(amount, 6) assert.NoError(t, err) expected := big.NewInt(-1230000) assert.Equal(t, expected.String(), result.String(), "Negative amounts should be handled correctly") @@ -321,7 +395,7 @@ func TestDecimalToBigInt_NegativeAmounts(t *testing.T) { t.Run("negative_eth", func(t *testing.T) { t.Parallel() amount := decimal.NewFromFloat(-0.5) - result, err := DecimalToBigInt(amount, 18) + result, err := decimalToBigInt(amount, 18) assert.NoError(t, err) expected, _ := new(big.Int).SetString("-500000000000000000", 10) assert.Equal(t, expected.String(), result.String(), "Negative ETH amount should convert correctly") @@ -330,7 +404,7 @@ func TestDecimalToBigInt_NegativeAmounts(t *testing.T) { t.Run("negative_zero", func(t *testing.T) { t.Parallel() amount := decimal.NewFromInt(0) - result, err := DecimalToBigInt(amount, 6) + result, err := decimalToBigInt(amount, 6) assert.NoError(t, err) expected := big.NewInt(0) assert.Equal(t, expected.String(), result.String(), "Zero should always be zero") @@ -344,7 +418,7 @@ func TestDecimalToBigInt_EdgeCases(t *testing.T) { // Test with a very large amount amount, err := decimal.NewFromString("999999999999999999.123456") assert.NoError(t, err) - result, err := DecimalToBigInt(amount, 6) + result, err := decimalToBigInt(amount, 6) assert.NoError(t, err) // 999999999999999999.123456 * 10^6 = 999999999999999999123456 expected, ok := new(big.Int).SetString("999999999999999999123456", 10) @@ -356,7 +430,7 @@ func TestDecimalToBigInt_EdgeCases(t *testing.T) { t.Parallel() // Test with an amount that has more decimals than supported amount := decimal.NewFromFloat(0.0000001) // 7 decimals - _, err := DecimalToBigInt(amount, 6) // Only 6 decimal precision + _, err := decimalToBigInt(amount, 6) // Only 6 decimal precision // This should return an error because the amount has more decimal places than allowed // After scaling: 0.0000001 * 10^6 = 0.1, which still has a fractional part assert.Error(t, err, "Amount with more decimals than supported should return an error") @@ -367,7 +441,7 @@ func TestDecimalToBigInt_EdgeCases(t *testing.T) { t.Parallel() // Test with maximum uint8 value for decimals (not practical, but edge case) amount := decimal.NewFromInt(1) - result, err := DecimalToBigInt(amount, 255) + result, err := decimalToBigInt(amount, 255) assert.NoError(t, err) // 1 * 10^255 should work expected := new(big.Int).Exp(big.NewInt(10), big.NewInt(255), nil) @@ -379,7 +453,7 @@ func TestDecimalToBigInt_EdgeCases(t *testing.T) { // Test that we don't lose precision during conversion amount, err := decimal.NewFromString("123.456789") assert.NoError(t, err) - result, err := DecimalToBigInt(amount, 6) + result, err := decimalToBigInt(amount, 6) assert.NoError(t, err) // 123.456789 * 10^6 = 123456789 expected := big.NewInt(123456789) @@ -397,7 +471,7 @@ func TestDecimalToBigInt_RoundTrip(t *testing.T) { assert.NoError(t, err) // Convert to big.Int - bigIntValue, err := DecimalToBigInt(amount, 6) + bigIntValue, err := decimalToBigInt(amount, 6) assert.NoError(t, err) // Convert back to decimal divisor := decimal.New(1, 6) // 10^6 @@ -650,3 +724,109 @@ func TestGetStateTransitionsHash(t *testing.T) { assert.NoError(t, err) }) } + +func TestDecimalToUint256(t *testing.T) { + t.Parallel() + + // 2^256 - 1, max uint256 + maxUint256, ok := new(big.Int).SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) + require.True(t, ok) + // 2^256, one above the limit + overflow := new(big.Int).Add(maxUint256, big.NewInt(1)) + + t.Run("zero", func(t *testing.T) { + t.Parallel() + got, err := DecimalToUint256(decimal.Zero, 18) + require.NoError(t, err) + assert.Equal(t, int64(0), got.Int64()) + }) + + t.Run("small_positive", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromInt(1234) + got, err := DecimalToUint256(amount, 0) + require.NoError(t, err) + assert.Equal(t, int64(1234), got.Int64()) + }) + + t.Run("max_uint256_accepted", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromBigInt(maxUint256, 0) + got, err := DecimalToUint256(amount, 0) + require.NoError(t, err) + assert.Equal(t, 0, got.Cmp(maxUint256)) + }) + + t.Run("one_over_uint256_rejected", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromBigInt(overflow, 0) + _, err := DecimalToUint256(amount, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "exceeds uint256 max") + }) + + t.Run("scaling_pushes_over_uint256", func(t *testing.T) { + t.Parallel() + // value at uint256 max with 1 extra decimal of scale exceeds the range. + amount := decimal.NewFromBigInt(maxUint256, 0) + _, err := DecimalToUint256(amount, 1) + require.Error(t, err) + assert.Contains(t, err.Error(), "exceeds uint256 max") + }) + + t.Run("negative_rejected", func(t *testing.T) { + t.Parallel() + _, err := DecimalToUint256(decimal.NewFromInt(-1), 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "negative") + }) +} + +func TestDecimalToInt256(t *testing.T) { + t.Parallel() + + maxInt, ok := new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819967", 10) // 2^255 - 1 + require.True(t, ok) + minInt := new(big.Int).Neg(new(big.Int).Lsh(big.NewInt(1), 255)) // -2^255 + overMax := new(big.Int).Add(maxInt, big.NewInt(1)) + underMin := new(big.Int).Sub(minInt, big.NewInt(1)) + + t.Run("zero", func(t *testing.T) { + t.Parallel() + got, err := DecimalToInt256(decimal.Zero, 18) + require.NoError(t, err) + assert.Equal(t, int64(0), got.Int64()) + }) + + t.Run("max_int256_accepted", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromBigInt(maxInt, 0) + got, err := DecimalToInt256(amount, 0) + require.NoError(t, err) + assert.Equal(t, 0, got.Cmp(maxInt)) + }) + + t.Run("min_int256_accepted", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromBigInt(minInt, 0) + got, err := DecimalToInt256(amount, 0) + require.NoError(t, err) + assert.Equal(t, 0, got.Cmp(minInt)) + }) + + t.Run("one_over_max_rejected", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromBigInt(overMax, 0) + _, err := DecimalToInt256(amount, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "exceeds int256 max") + }) + + t.Run("one_under_min_rejected", func(t *testing.T) { + t.Parallel() + amount := decimal.NewFromBigInt(underMin, 0) + _, err := DecimalToInt256(amount, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "below int256 min") + }) +} diff --git a/pkg/rpc/README.md b/pkg/rpc/README.md index 642b29ac6..092f158f9 100644 --- a/pkg/rpc/README.md +++ b/pkg/rpc/README.md @@ -273,7 +273,7 @@ go func() { // Connect to server ctx := context.Background() -err := client.Start(ctx, "wss://clearnode-sandbox.yellow.org/v1/ws", func(err error) { +err := client.Start(ctx, "wss://nitronode-sandbox.yellow.org/v1/ws", func(err error) { if err != nil { log.Error("Connection closed", "error", err) } @@ -350,12 +350,6 @@ state, err := client.ChannelsV1GetLatestState(ctx, rpc.ChannelsV1GetLatestStateR Asset: "usdc", }) -// Get states with filters -states, err := client.ChannelsV1GetStates(ctx, rpc.ChannelsV1GetStatesRequest{ - Wallet: walletAddress, - Asset: &asset, -}) - // Request channel creation creation, err := client.ChannelsV1RequestCreation(ctx, rpc.ChannelsV1RequestCreationRequest{ Wallet: walletAddress, @@ -385,11 +379,6 @@ session, err := client.AppSessionsV1CreateAppSession(ctx, rpc.AppSessionsV1Creat Definition: definition, }) -// Close app session -closeResp, err := client.AppSessionsV1CloseAppSession(ctx, rpc.AppSessionsV1CloseAppSessionRequest{ - AppSessionID: sessionID, -}) - // Submit deposit state depositResp, err := client.AppSessionsV1SubmitDepositState(ctx, rpc.AppSessionsV1SubmitDepositStateRequest{ AppSessionID: sessionID, diff --git a/pkg/rpc/api.go b/pkg/rpc/api.go index 72b364191..d54c98c0c 100644 --- a/pkg/rpc/api.go +++ b/pkg/rpc/api.go @@ -22,9 +22,11 @@ type ChannelsV1GetHomeChannelRequest struct { } // ChannelsV1GetHomeChannelResponse returns the on-chain channel information. +// Channel is nil when no home channel exists for the given wallet/asset pair; +// this is a successful response, not an error. type ChannelsV1GetHomeChannelResponse struct { - // Channel is the on-chain channel information - Channel ChannelV1 `json:"channel"` + // Channel is the on-chain channel information, or nil if absent. + Channel *ChannelV1 `json:"channel,omitempty"` } // ChannelsV1GetEscrowChannelRequest retrieves current on-chain escrow channel information. @@ -34,9 +36,11 @@ type ChannelsV1GetEscrowChannelRequest struct { } // ChannelsV1GetEscrowChannelResponse returns the on-chain channel information. +// Channel is nil when no escrow channel exists for the given ID; this is a +// successful response, not an error. type ChannelsV1GetEscrowChannelResponse struct { - // Channel is the on-chain channel information - Channel ChannelV1 `json:"channel"` + // Channel is the on-chain channel information, or nil if absent. + Channel *ChannelV1 `json:"channel,omitempty"` } // ChannelsV1GetChannelsRequest retrieves all channels for a user with optional filtering. @@ -72,33 +76,11 @@ type ChannelsV1GetLatestStateRequest struct { } // ChannelsV1GetLatestStateResponse returns the current state of the user. +// State is nil when no state has been stored for the given wallet/asset pair; +// this is a successful response, not an error. type ChannelsV1GetLatestStateResponse struct { - // State is the current state of the user - State StateV1 `json:"state"` -} - -// ChannelsV1GetStatesRequest retrieves state history for a user with optional filtering. -type ChannelsV1GetStatesRequest struct { - // Wallet is the user's wallet address - Wallet string `json:"wallet"` - // Asset filters by asset symbol - Asset string `json:"asset"` - // Epoch filters by user epoch index - Epoch *string `json:"epoch,omitempty"` - // ChannelID filters by Home/Escrow Channel ID - ChannelID *string `json:"channel_id,omitempty"` - // OnlySigned returns only signed states - OnlySigned bool `json:"only_signed"` - // Pagination contains pagination parameters (offset, limit, sort) - Pagination *PaginationParamsV1 `json:"pagination,omitempty"` -} - -// ChannelsV1GetStatesResponse returns the list of states. -type ChannelsV1GetStatesResponse struct { - // States is the list of states - States []StateV1 `json:"states"` - // Metadata contains pagination information - Metadata PaginationMetadataV1 `json:"metadata"` + // State is the current state of the user, or nil if absent. + State *StateV1 `json:"state,omitempty"` } // ChannelsV1RequestCreationRequest requests the creation of a channel from Node. @@ -135,7 +117,10 @@ type ChannelsV1HomeChannelCreatedEvent struct { InitialState StateV1 `json:"initial_state"` } -// ChannelsV1SubmitSessionKeyStateRequest submits the session key state for registration and updates. +// ChannelsV1SubmitSessionKeyStateRequest submits a channel session key state for registration, +// rotation/update, or revocation. A submit whose ExpiresAt is in the past (<= server's now) +// is treated as a revocation: the auth path stops accepting state signed by the key and the +// slot is freed against the per-user cap. type ChannelsV1SubmitSessionKeyStateRequest struct { // State contains the session key metadata and delegation information State ChannelSessionKeyStateV1 `json:"state"` @@ -150,12 +135,19 @@ type ChannelsV1GetLastKeyStatesRequest struct { // UserAddress is the user's wallet address UserAddress string `json:"user_address"` SessionKey *string `json:"session_key,omitempty"` // Optionally filter by SessionKey + // IncludeInactive, when true, includes latest states whose expires_at is in the past + // (expired or revoked). Defaults to false: only currently active states are returned. + IncludeInactive *bool `json:"include_inactive,omitempty"` + // Pagination contains pagination parameters (offset, limit, sort) + Pagination *PaginationParamsV1 `json:"pagination,omitempty"` } -// ChannelsV1GetSessionKeysResponse returns the list of active session keys. +// ChannelsV1GetLastKeyStatesResponse returns the latest session key states for the user. type ChannelsV1GetLastKeyStatesResponse struct { - // States is the list of active session key states for the user + // States is the list of latest session key states for the user, filtered by IncludeInactive. States []ChannelSessionKeyStateV1 `json:"states"` + // Metadata contains pagination information + Metadata PaginationMetadataV1 `json:"metadata"` } // ============================================================================ @@ -168,7 +160,7 @@ type AppSessionsV1SubmitDepositStateRequest struct { AppStateUpdate AppStateUpdateV1 `json:"app_state_update"` // QuorumSigs is the list of participant signatures for the app state update QuorumSigs []string `json:"quorum_sigs"` - // SigQuorum is the signature quorum for the application session + // UserState is the signed channel state from the user, used to fund the application session deposit UserState StateV1 `json:"user_state"` } @@ -216,9 +208,11 @@ type AppSessionsV1GetAppDefinitionRequest struct { } // AppSessionsV1GetAppDefinitionResponse returns the application definition. +// Definition is nil when no app session exists for the given ID; this is a +// successful response, not an error. type AppSessionsV1GetAppDefinitionResponse struct { - // Definition is the application definition - Definition AppDefinitionV1 `json:"definition"` + // Definition is the application definition, or nil if absent. + Definition *AppDefinitionV1 `json:"definition,omitempty"` } // AppSessionsV1GetAppSessionsRequest lists all application sessions for a participant with optional filtering. @@ -263,7 +257,10 @@ type AppSessionsV1CreateAppSessionResponse struct { Status string `json:"status"` } -// AppSessionsV1SubmitSessionKeyStateRequest submits the session key state for registration and updates. +// AppSessionsV1SubmitSessionKeyStateRequest submits an app session key state for registration, +// rotation/update, or revocation. A submit whose ExpiresAt is in the past (<= server's now) +// is treated as a revocation: the auth path stops accepting state signed by the key and the +// slot is freed against the per-user cap. type AppSessionsV1SubmitSessionKeyStateRequest struct { // State contains the session key metadata and delegation information State AppSessionKeyStateV1 `json:"state"` @@ -278,12 +275,19 @@ type AppSessionsV1GetLastKeyStatesRequest struct { // UserAddress is the user's wallet address UserAddress string `json:"user_address"` SessionKey *string `json:"session_key,omitempty"` // Optionally filter by SessionKey + // IncludeInactive, when true, includes latest states whose expires_at is in the past + // (expired or revoked). Defaults to false: only currently active states are returned. + IncludeInactive *bool `json:"include_inactive,omitempty"` + // Pagination contains pagination parameters (offset, limit, sort) + Pagination *PaginationParamsV1 `json:"pagination,omitempty"` } -// SessionKeysV1GetSessionKeysResponse returns the list of active session keys. +// AppSessionsV1GetLastKeyStatesResponse returns the latest session key states for the user. type AppSessionsV1GetLastKeyStatesResponse struct { - // States is the list of active session key states for the user + // States is the list of latest session key states for the user, filtered by IncludeInactive. States []AppSessionKeyStateV1 `json:"states"` + // Metadata contains pagination information + Metadata PaginationMetadataV1 `json:"metadata"` } // ============================================================================ diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index f479d0da6..6e4eefa59 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://clearnode-sandbox.yellow.org/v1/ws", handleError) +// err := client.Start(ctx, "wss://nitronode-sandbox.yellow.org/v1/ws", handleError) // if err != nil { // log.Fatal(err) // } @@ -96,15 +96,6 @@ func (c *Client) ChannelsV1GetLatestState(ctx context.Context, req ChannelsV1Get return resp, nil } -// ChannelsV1GetStates retrieves state history for a user with optional filtering. -func (c *Client) ChannelsV1GetStates(ctx context.Context, req ChannelsV1GetStatesRequest) (ChannelsV1GetStatesResponse, error) { - var resp ChannelsV1GetStatesResponse - if err := c.call(ctx, ChannelsV1GetStatesMethod, req, &resp); err != nil { - return resp, err - } - return resp, nil -} - // ChannelsV1RequestCreation requests the creation of a channel from Node. func (c *Client) ChannelsV1RequestCreation(ctx context.Context, req ChannelsV1RequestCreationRequest) (ChannelsV1RequestCreationResponse, error) { var resp ChannelsV1RequestCreationResponse diff --git a/pkg/rpc/client_test.go b/pkg/rpc/client_test.go index 658e35e1e..d36242663 100644 --- a/pkg/rpc/client_test.go +++ b/pkg/rpc/client_test.go @@ -137,7 +137,7 @@ func TestClientV1_ChannelsV1GetHomeChannel(t *testing.T) { client, dialer := setupClient() channel := rpc.ChannelsV1GetHomeChannelResponse{ - Channel: rpc.ChannelV1{ + Channel: &rpc.ChannelV1{ ChannelID: testChannelID, UserWallet: testWalletV1, Type: "home", @@ -161,13 +161,29 @@ func TestClientV1_ChannelsV1GetHomeChannel(t *testing.T) { assert.Equal(t, "home", resp.Channel.Type) } +// TestClientV1_ChannelsV1GetHomeChannel_NilChannel verifies absent-channel responses decode to a nil pointer without error. +func TestClientV1_ChannelsV1GetHomeChannel_NilChannel(t *testing.T) { + t.Parallel() + + client, dialer := setupClient() + + registerSimpleHandlerV1(dialer, "channels.v1.get_home_channel", rpc.ChannelsV1GetHomeChannelResponse{}) + + resp, err := client.ChannelsV1GetHomeChannel(testCtxV1, rpc.ChannelsV1GetHomeChannelRequest{ + Wallet: testWalletV1, + Asset: testAssetV1, + }) + require.NoError(t, err) + assert.Nil(t, resp.Channel) +} + func TestClientV1_ChannelsV1GetEscrowChannel(t *testing.T) { t.Parallel() client, dialer := setupClient() channel := rpc.ChannelsV1GetEscrowChannelResponse{ - Channel: rpc.ChannelV1{ + Channel: &rpc.ChannelV1{ ChannelID: testChannelID, Type: "escrow", BlockchainID: testChainIDV1, @@ -184,6 +200,21 @@ func TestClientV1_ChannelsV1GetEscrowChannel(t *testing.T) { assert.Equal(t, "escrow", resp.Channel.Type) } +// TestClientV1_ChannelsV1GetEscrowChannel_NilChannel verifies absent-channel responses decode to a nil pointer without error. +func TestClientV1_ChannelsV1GetEscrowChannel_NilChannel(t *testing.T) { + t.Parallel() + + client, dialer := setupClient() + + registerSimpleHandlerV1(dialer, "channels.v1.get_escrow_channel", rpc.ChannelsV1GetEscrowChannelResponse{}) + + resp, err := client.ChannelsV1GetEscrowChannel(testCtxV1, rpc.ChannelsV1GetEscrowChannelRequest{ + EscrowChannelID: testChannelID, + }) + require.NoError(t, err) + assert.Nil(t, resp.Channel) +} + func TestClientV1_ChannelsV1GetChannels(t *testing.T) { t.Parallel() @@ -218,7 +249,7 @@ func TestClientV1_ChannelsV1GetLatestState(t *testing.T) { client, dialer := setupClient() state := rpc.ChannelsV1GetLatestStateResponse{ - State: rpc.StateV1{ + State: &rpc.StateV1{ ID: "state123", Asset: testAssetV1, UserWallet: testWalletV1, @@ -245,33 +276,21 @@ func TestClientV1_ChannelsV1GetLatestState(t *testing.T) { assert.Equal(t, testAssetV1, resp.State.Asset) } -func TestClientV1_ChannelsV1GetStates(t *testing.T) { +// TestClientV1_ChannelsV1GetLatestState_NilState verifies absent-state responses decode to a nil pointer without error. +func TestClientV1_ChannelsV1GetLatestState_NilState(t *testing.T) { t.Parallel() client, dialer := setupClient() - states := rpc.ChannelsV1GetStatesResponse{ - States: []rpc.StateV1{ - {ID: "state1", Version: "1", Asset: testAssetV1}, - {ID: "state2", Version: "2", Asset: testAssetV1}, - }, - Metadata: rpc.PaginationMetadataV1{ - Page: 1, - PerPage: 10, - TotalCount: 2, - PageCount: 1, - }, - } - - registerSimpleHandlerV1(dialer, "channels.v1.get_states", states) + registerSimpleHandlerV1(dialer, "channels.v1.get_latest_state", rpc.ChannelsV1GetLatestStateResponse{}) - resp, err := client.ChannelsV1GetStates(testCtxV1, rpc.ChannelsV1GetStatesRequest{ + resp, err := client.ChannelsV1GetLatestState(testCtxV1, rpc.ChannelsV1GetLatestStateRequest{ Wallet: testWalletV1, Asset: testAssetV1, OnlySigned: false, }) require.NoError(t, err) - assert.Len(t, resp.States, 2) + assert.Nil(t, resp.State) } func TestClientV1_ChannelsV1RequestCreation(t *testing.T) { @@ -331,7 +350,7 @@ func TestClientV1_AppSessionsV1GetAppDefinition(t *testing.T) { client, dialer := setupClient() definition := rpc.AppSessionsV1GetAppDefinitionResponse{ - Definition: rpc.AppDefinitionV1{ + Definition: &rpc.AppDefinitionV1{ Application: "game", Participants: []rpc.AppParticipantV1{ {WalletAddress: testWalletV1, SignatureWeight: 1}, @@ -352,6 +371,21 @@ func TestClientV1_AppSessionsV1GetAppDefinition(t *testing.T) { assert.Len(t, resp.Definition.Participants, 2) } +// TestClientV1_AppSessionsV1GetAppDefinition_NilDefinition verifies absent-definition responses decode to a nil pointer without error. +func TestClientV1_AppSessionsV1GetAppDefinition_NilDefinition(t *testing.T) { + t.Parallel() + + client, dialer := setupClient() + + registerSimpleHandlerV1(dialer, "app_sessions.v1.get_app_definition", rpc.AppSessionsV1GetAppDefinitionResponse{}) + + resp, err := client.AppSessionsV1GetAppDefinition(testCtxV1, rpc.AppSessionsV1GetAppDefinitionRequest{ + AppSessionID: testAppSession, + }) + require.NoError(t, err) + assert.Nil(t, resp.Definition) +} + func TestClientV1_AppSessionsV1GetAppSessions(t *testing.T) { t.Parallel() diff --git a/pkg/rpc/connection.go b/pkg/rpc/connection.go index 273e01701..e213da262 100644 --- a/pkg/rpc/connection.go +++ b/pkg/rpc/connection.go @@ -2,6 +2,7 @@ package rpc import ( "context" + "errors" "fmt" "io" "sync" @@ -26,6 +27,9 @@ var ( // defaultWsConnPongTimeout is the default timeout for receiving pong responses from clients. // If no pong is received within this duration after a ping, the connection is considered dead. defaultWsConnPongTimeout = 10 * time.Second + // defaultWsConnMaxMessageSize is the default cap on inbound WebSocket frame size in bytes. + // Frames exceeding this trigger close 1009 (Message Too Big) before allocation. + defaultWsConnMaxMessageSize int64 = 128 * 1024 ) // Connection represents an active RPC connection that handles bidirectional communication. @@ -40,6 +44,10 @@ type Connection interface { // Origin returns the origin of the connection, such as the client's IP address or other identifying information. Origin() string + // ApplicationID returns the application identifier supplied at connection time (via the + // app_id query parameter). Returns an empty string if no application_id was provided. + ApplicationID() string + // RawRequests returns a read-only channel for receiving incoming raw request messages. // Messages received on this channel are raw bytes that need to be unmarshaled // into Request objects for processing. The channel is closed when the @@ -75,6 +83,10 @@ type GorillaWsConnectionAdapter interface { // SetReadDeadline sets the deadline for future Read calls. // A zero value means reads will not time out. SetReadDeadline(t time.Time) error + // SetReadLimit sets the maximum size in bytes for a single inbound message. + // Frames exceeding the limit cause ReadMessage to return a *CloseError with + // code 1009 (CloseMessageTooBig) and the connection sends a close frame. + SetReadLimit(limit int64) } // WebsocketConnection implements the Connection interface using WebSocket transport. @@ -96,6 +108,8 @@ type WebsocketConnection struct { connectionID string // origin is the origin of the connection, such as the client's IP address origin string + // applicationID is the app_id query parameter supplied at WebSocket upgrade (may be empty) + applicationID string // websocketConn is the underlying WebSocket connection websocketConn GorillaWsConnectionAdapter // writeTimeout is the maximum duration to wait for a write to complete @@ -104,6 +118,11 @@ type WebsocketConnection struct { pingInterval time.Duration // pongTimeout is the maximum duration to wait for a pong response from the client pongTimeout time.Duration + // maxMessageSize caps inbound frame size in bytes. Always positive after + // NewWebsocketConnection: non-positive config values fall back to the default. + maxMessageSize int64 + // frameRateLimiter decides whether each inbound frame is admitted. + frameRateLimiter FrameRateLimiter // logger is used for logging events related to this connection logger log.Logger @@ -127,6 +146,9 @@ type WebsocketConnectionConfig struct { ConnectionID string // Origin is the origin of the connection, such as the client's IP address (optional) Origin string + // ApplicationID is the app_id query parameter supplied at WebSocket upgrade (optional). + // Caller is responsible for validation; the connection stores it as-is for metrics labeling. + ApplicationID string // WebsocketConn is the underlying WebSocket connection (required) WebsocketConn GorillaWsConnectionAdapter @@ -141,6 +163,14 @@ type WebsocketConnectionConfig struct { // PongTimeout is the maximum duration to wait for a pong response from the client (default: 10s). // If no pong is received within this duration, the connection is considered dead. PongTimeout time.Duration + // MaxMessageSize caps inbound frame size in bytes (default: 128 KiB). + // Frames larger than this are rejected with WebSocket close code 1009 + // before any allocation grows past the limit. Non-positive values fall + // back to the default; the cap cannot be disabled at this layer. + MaxMessageSize int64 + // FrameRateLimiter is consulted for every inbound frame; returning false closes + // the connection. nil → NoopFrameRateLimiter (no enforcement). + FrameRateLimiter FrameRateLimiter // Logger for connection events (default: no-op logger) Logger log.Logger // OnMessageSentHandler is called after a message is successfully sent (optional) @@ -178,17 +208,26 @@ func NewWebsocketConnection(config WebsocketConnectionConfig) (*WebsocketConnect if config.PongTimeout <= 0 { config.PongTimeout = defaultWsConnPongTimeout } + if config.MaxMessageSize <= 0 { + config.MaxMessageSize = defaultWsConnMaxMessageSize + } + if config.FrameRateLimiter == nil { + config.FrameRateLimiter = NoopFrameRateLimiter{} + } if config.OnMessageSentHandler == nil { config.OnMessageSentHandler = func([]byte) {} } return &WebsocketConnection{ - connectionID: config.ConnectionID, - origin: config.Origin, - websocketConn: config.WebsocketConn, - writeTimeout: config.WriteTimeout, - pingInterval: config.PingInterval, - pongTimeout: config.PongTimeout, + connectionID: config.ConnectionID, + origin: config.Origin, + applicationID: config.ApplicationID, + websocketConn: config.WebsocketConn, + writeTimeout: config.WriteTimeout, + pingInterval: config.PingInterval, + pongTimeout: config.PongTimeout, + maxMessageSize: config.MaxMessageSize, + frameRateLimiter: config.FrameRateLimiter, logger: config.Logger.WithKV("connectionID", config.ConnectionID), onMessageSentHandler: config.OnMessageSentHandler, @@ -219,6 +258,13 @@ func (conn *WebsocketConnection) Serve(parentCtx context.Context, handleClosure conn.ctx = parentCtx conn.mu.Unlock() + // Cap inbound frame size before any read takes place. Exceeding the limit + // causes the next ReadMessage to return *CloseError{Code: 1009} and the + // underlying connection sends a close frame to the client. + if conn.maxMessageSize > 0 { + conn.websocketConn.SetReadLimit(conn.maxMessageSize) + } + // Set up pong handler to refresh read deadline when pong is received from client. // This enables detection of dead connections - if no pong arrives within the timeout // after sending a ping, the read will fail and the connection will be closed. @@ -285,6 +331,12 @@ func (conn *WebsocketConnection) Origin() string { return conn.origin } +// ApplicationID returns the app_id query parameter supplied at WebSocket upgrade, +// or an empty string if none was provided. +func (conn *WebsocketConnection) ApplicationID() string { + return conn.applicationID +} + // RawRequests returns the channel for processing incoming requests. func (conn *WebsocketConnection) RawRequests() <-chan []byte { return conn.processSink @@ -322,10 +374,27 @@ func (conn *WebsocketConnection) readMessages(handleClosure func(error)) { for { _, messageBytes, err := conn.websocketConn.ReadMessage() if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNormalClosure) { + switch { + case errors.Is(err, websocket.ErrReadLimit), + websocket.IsCloseError(err, websocket.CloseMessageTooBig): + // Expected attacker / misconfigured-client path. Local read limit + // exceeded → gorilla returns ErrReadLimit to us and best-effort + // sends close 1009 to the peer. The IsCloseError branch covers + // the symmetric case where the peer initiated a 1009 close. + // Treat both as graceful close, not abnormal termination. + conn.logger.Warn("inbound frame exceeded MaxMessageSize; closing connection", + "error", err, + "origin", conn.origin, + "max_message_size", conn.maxMessageSize, + ) + handleClosure(nil) + case websocket.IsUnexpectedCloseError(err, + websocket.CloseGoingAway, + websocket.CloseAbnormalClosure, + websocket.CloseNormalClosure): conn.logger.Error("WebSocket connection closed with unexpected reason", "error", err) handleClosure(err) - } else { + default: handleClosure(nil) // Normal closure } return @@ -336,6 +405,19 @@ func (conn *WebsocketConnection) readMessages(handleClosure func(error)) { continue // Skip empty messages } + if !conn.frameRateLimiter.Admit(time.Now(), len(messageBytes)) { + conn.logger.Warn("frame rate limit exceeded; closing connection", + "origin", conn.origin, + "frame_bytes", len(messageBytes), + ) + select { + case conn.closeConnCh <- struct{}{}: + default: + } + handleClosure(nil) + return + } + select { case conn.processSink <- messageBytes: // ok @@ -348,6 +430,7 @@ func (conn *WebsocketConnection) readMessages(handleClosure func(error)) { case conn.closeConnCh <- struct{}{}: default: } + handleClosure(nil) return } } diff --git a/pkg/rpc/connection_hub.go b/pkg/rpc/connection_hub.go index b92c5491b..83587761f 100644 --- a/pkg/rpc/connection_hub.go +++ b/pkg/rpc/connection_hub.go @@ -5,9 +5,10 @@ import ( "sync" ) -const defaultConnectionRegion = "default" - -type ObserveConnectionsFn func(region, origin string, count uint32) +// ObserveConnectionsFn is invoked on connect and disconnect with the current per-application +// connection count. A count of 0 signals that the bucket is empty and the observer should +// shed any per-label state (e.g., delete the Prometheus gauge series) to bound cardinality. +type ObserveConnectionsFn func(applicationID string, count uint32) // ConnectionHub provides centralized management of all active RPC connections. // It maintains thread-safe mappings between connection IDs and Connection instances, @@ -28,9 +29,9 @@ type ConnectionHub struct { // mu protects concurrent access to the maps mu sync.RWMutex - // sourceMap is an optional mapping of connection sources (e.g., IP addresses or regions) - sourceMap map[string]uint32 - // observeConnections is a callback function to monitor connection counts by region + // appConnCount tracks active connection counts keyed by application_id (may be empty string) + appConnCount map[string]uint32 + // observeConnections is a callback function to report per-application connection counts observeConnections ObserveConnectionsFn } @@ -41,7 +42,7 @@ func NewConnectionHub(observeConnections ObserveConnectionsFn) *ConnectionHub { return &ConnectionHub{ connections: make(map[string]Connection), authMapping: make(map[string]map[string]bool), - sourceMap: make(map[string]uint32), + appConnCount: make(map[string]uint32), observeConnections: observeConnections, } } @@ -60,18 +61,23 @@ func (hub *ConnectionHub) Add(conn Connection) error { connID := conn.ConnectionID() hub.mu.Lock() - defer hub.mu.Unlock() // If the connection already exists, return an error if _, exists := hub.connections[connID]; exists { + hub.mu.Unlock() return fmt.Errorf("connection with ID %s already exists", connID) } hub.connections[connID] = conn - sourceID := getSourceID(conn.Origin()) - hub.sourceMap[sourceID]++ - hub.observeConnections(defaultConnectionRegion, conn.Origin(), uint32(hub.sourceMap[sourceID])) + appID := conn.ApplicationID() + hub.appConnCount[appID]++ + count := hub.appConnCount[appID] + hub.mu.Unlock() + + // Invoke the observer outside the lock: SetRPCConnections takes Prometheus-internal + // mutexes, and holding hub.mu across that would serialize readers (including Publish). + hub.observeConnections(appID, count) return nil } @@ -103,22 +109,33 @@ func (hub *ConnectionHub) Get(connID string) Connection { // This method is safe for concurrent access. func (hub *ConnectionHub) Remove(connID string) { hub.mu.Lock() - defer hub.mu.Unlock() conn, exists := hub.connections[connID] if !exists { + hub.mu.Unlock() return // No connection to remove } delete(hub.connections, connID) - sourceID := getSourceID(conn.Origin()) - if count, exists := hub.sourceMap[sourceID]; exists && count > 0 { - hub.sourceMap[sourceID]-- - if hub.sourceMap[sourceID] == 0 { - delete(hub.sourceMap, sourceID) + appID := conn.ApplicationID() + count, tracked := hub.appConnCount[appID] + changed := false + if tracked && count > 0 { + hub.appConnCount[appID]-- + count = hub.appConnCount[appID] + if count == 0 { + delete(hub.appConnCount, appID) } + changed = true + } + hub.mu.Unlock() + + // Only notify the observer when the bucket actually changed; otherwise we would + // emit DeleteLabelValues for an app_id the gauge never tracked. Invoke outside + // hub.mu to avoid serializing readers behind Prometheus-internal locks. + if changed { + hub.observeConnections(appID, count) } - hub.observeConnections(defaultConnectionRegion, conn.Origin(), uint32(hub.sourceMap[sourceID])) } // Publish broadcasts a message to all active connections for a specific user. @@ -153,6 +170,3 @@ func (hub *ConnectionHub) Publish(userID string, response []byte) { } } -func getSourceID(origin string) string { - return origin -} diff --git a/pkg/rpc/connection_test.go b/pkg/rpc/connection_test.go index 294073dc7..4c5987b42 100644 --- a/pkg/rpc/connection_test.go +++ b/pkg/rpc/connection_test.go @@ -92,6 +92,179 @@ func TestWebsocketConnection_Serve(t *testing.T) { require.Equal(t, 2, wsConnMock.getCalledCloseCount()) } +func TestWebsocketConnection_Serve_AppliesReadLimit(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wsConnMock := newGorillaWsConnMock(ctx) + cfg := rpc.WebsocketConnectionConfig{ + ConnectionID: "conn-readlimit", + WebsocketConn: wsConnMock, + MaxMessageSize: 64 * 1024, + } + conn, err := rpc.NewWebsocketConnection(cfg) + require.NoError(t, err) + + conn.Serve(ctx, func(error) {}) + + // Serve calls SetReadLimit synchronously before spawning any goroutine, so + // the limit is observable immediately on return — no polling needed. + require.Equal(t, int64(64*1024), wsConnMock.getReadLimit()) +} + +// TestWebsocketConnection_LocalReadLimit_GracefulClose simulates the local +// SetReadLimit hitting on an inbound frame. Gorilla returns ErrReadLimit to +// the application (and best-effort sends close 1009 to the peer over the wire). +// The connection must treat this as a graceful close, not an abnormal error. +func TestWebsocketConnection_LocalReadLimit_GracefulClose(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wsConnMock := newGorillaWsConnMock(ctx) + cfg := rpc.WebsocketConnectionConfig{ + ConnectionID: "conn-readlimit", + WebsocketConn: wsConnMock, + } + conn, err := rpc.NewWebsocketConnection(cfg) + require.NoError(t, err) + + closureCh := make(chan error, 1) + conn.Serve(ctx, func(err error) { closureCh <- err }) + + wsConnMock.readErrCh <- websocket.ErrReadLimit + + select { + case err := <-closureCh: + require.NoError(t, err, "ErrReadLimit must close gracefully, not as abnormal error") + case <-time.After(500 * time.Millisecond): + t.Fatal("connection did not close after local read-limit hit") + } +} + +// TestWebsocketConnection_PeerInitiated1009_GracefulClose covers the symmetric +// case: the peer initiates a close with code 1009 (their read limit hit). +// Gorilla surfaces a *CloseError{1009} on ReadMessage in that case. +func TestWebsocketConnection_PeerInitiated1009_GracefulClose(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wsConnMock := newGorillaWsConnMock(ctx) + cfg := rpc.WebsocketConnectionConfig{ + ConnectionID: "conn-peer1009", + WebsocketConn: wsConnMock, + } + conn, err := rpc.NewWebsocketConnection(cfg) + require.NoError(t, err) + + closureCh := make(chan error, 1) + conn.Serve(ctx, func(err error) { closureCh <- err }) + + wsConnMock.readErrCh <- &websocket.CloseError{ + Code: websocket.CloseMessageTooBig, + Text: "peer-side read limit exceeded", + } + + select { + case err := <-closureCh: + require.NoError(t, err, "peer-sent 1009 must close gracefully, not as abnormal error") + case <-time.After(500 * time.Millisecond): + t.Fatal("connection did not close after peer-initiated 1009") + } +} + +// stubLimiter rejects after the Nth call. Records every call for assertions. +type stubLimiter struct { + rejectAt int + calls int +} + +func (s *stubLimiter) Admit(_ time.Time, _ int) bool { + s.calls++ + return s.calls < s.rejectAt +} + +func TestWebsocketConnection_RateLimitedFrame_ClosesConnection(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wsConnMock := newGorillaWsConnMock(ctx) + limiter := &stubLimiter{rejectAt: 2} // first frame admits, second rejects + cfg := rpc.WebsocketConnectionConfig{ + ConnectionID: "conn-ratelimit", + WebsocketConn: wsConnMock, + FrameRateLimiter: limiter, + } + conn, err := rpc.NewWebsocketConnection(cfg) + require.NoError(t, err) + + closureCh := make(chan error, 1) + conn.Serve(ctx, func(err error) { closureCh <- err }) + + // First frame admitted, drained by reader. + wsConnMock.addMessageToRead("ok") + select { + case got := <-conn.RawRequests(): + require.Equal(t, "ok", string(got)) + case <-time.After(200 * time.Millisecond): + t.Fatal("first frame not delivered") + } + + // Second frame rejected by limiter → connection should close. + wsConnMock.addMessageToRead("blocked") + + select { + case err := <-closureCh: + require.NoError(t, err, "rate-limit close is graceful") + case <-time.After(500 * time.Millisecond): + t.Fatal("connection did not close after rate-limited frame") + } + require.Equal(t, 2, limiter.calls, "limiter was consulted for both frames") +} + +func TestWebsocketConnection_ServeQueueFullClosesCleanly(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wsConnMock := newGorillaWsConnMock(ctx) + + cfg := rpc.WebsocketConnectionConfig{ + ConnectionID: "conn1", + WebsocketConn: wsConnMock, + ProcessBufferSize: 1, + } + conn, err := rpc.NewWebsocketConnection(cfg) + require.NoError(t, err) + + closed := make(chan struct{}) + conn.Serve(ctx, func(error) { close(closed) }) + + // First message fills processSink (no consumer reads RawRequests). + wsConnMock.addMessageToRead("msg1") + // Second message hits the queue-full branch. Without a handleClosure call, + // the internal wait group blocks forever and the parent closure never fires. + wsConnMock.addMessageToRead("msg2") + + select { + case <-closed: + case <-time.After(2 * time.Second): + t.Fatal("parent handleClosure not invoked after processSink overflow; goroutine leak") + } + + require.Eventually(t, func() bool { + return wsConnMock.getCalledCloseCount() == 1 + }, time.Second, 10*time.Millisecond, "underlying WebSocket Close() not called") +} + func TestWebsocketConnection_ConnectionID(t *testing.T) { t.Parallel() @@ -125,8 +298,10 @@ func TestWebsocketConnection_WriteRawResponse(t *testing.T) { type gorillaWsConnMock struct { ctx context.Context messageToReadCh chan []byte + readErrCh chan error lastWrittenMessage []byte calledCloseCount int + readLimit int64 mu sync.Mutex } @@ -135,6 +310,7 @@ func newGorillaWsConnMock(ctx context.Context) *gorillaWsConnMock { return &gorillaWsConnMock{ ctx: ctx, messageToReadCh: make(chan []byte, 1), + readErrCh: make(chan error, 1), } } @@ -145,6 +321,8 @@ func (m *gorillaWsConnMock) ReadMessage() (messageType int, p []byte, err error) Code: websocket.CloseNormalClosure, Text: "context cancelled", } + case err := <-m.readErrCh: + return 0, nil, err case msg := <-m.messageToReadCh: // Simulate reading a message return websocket.TextMessage, msg, nil @@ -202,3 +380,17 @@ func (m *gorillaWsConnMock) SetReadDeadline(t time.Time) error { // No-op for mock return nil } + +func (m *gorillaWsConnMock) SetReadLimit(limit int64) { + m.mu.Lock() + defer m.mu.Unlock() + + m.readLimit = limit +} + +func (m *gorillaWsConnMock) getReadLimit() int64 { + m.mu.Lock() + defer m.mu.Unlock() + + return m.readLimit +} diff --git a/pkg/rpc/context.go b/pkg/rpc/context.go index 1981d611a..ac9555f16 100644 --- a/pkg/rpc/context.go +++ b/pkg/rpc/context.go @@ -5,6 +5,30 @@ import ( "sync" ) +// ApplicationIDQueryParam is the URL query parameter clients use to declare +// their application identity during the WebSocket upgrade (e.g. +// ws://host/?app_id=my-app). The same key is used to store the value in +// per-connection SafeStorage. +const ApplicationIDQueryParam = "app_id" + +// GetApplicationID returns the application identifier associated with the current +// connection (supplied by the client as the ApplicationIDQueryParam query parameter +// during the WebSocket upgrade). Returns an empty string if no app_id was provided. +// +// The value is an advisory origin tag — it is self-declared by the client and +// must not be used for authentication or access control. +func GetApplicationID(c *Context) string { + if c == nil || c.Storage == nil { + return "" + } + v, ok := c.Storage.Get(ApplicationIDQueryParam) + if !ok { + return "" + } + s, _ := v.(string) + return s +} + // Handler defines the function signature for RPC request processors. // Handlers receive a Context containing the request and all necessary // information to process it. They can call c.Next() to delegate to diff --git a/pkg/rpc/context_internal_test.go b/pkg/rpc/context_internal_test.go index 04b4ff26a..f78338eac 100644 --- a/pkg/rpc/context_internal_test.go +++ b/pkg/rpc/context_internal_test.go @@ -137,6 +137,35 @@ func TestContext_Fail(t *testing.T) { }) } +func TestGetApplicationID(t *testing.T) { + t.Parallel() + + t.Run("returns empty for nil context", func(t *testing.T) { + assert.Equal(t, "", GetApplicationID(nil)) + }) + + t.Run("returns empty for nil storage", func(t *testing.T) { + assert.Equal(t, "", GetApplicationID(&Context{})) + }) + + t.Run("returns empty when unset", func(t *testing.T) { + ctx := &Context{Storage: NewSafeStorage()} + assert.Equal(t, "", GetApplicationID(ctx)) + }) + + t.Run("returns value when set", func(t *testing.T) { + ctx := &Context{Storage: NewSafeStorage()} + ctx.Storage.Set(ApplicationIDQueryParam, "my-app") + assert.Equal(t, "my-app", GetApplicationID(ctx)) + }) + + t.Run("returns empty when value is not a string", func(t *testing.T) { + ctx := &Context{Storage: NewSafeStorage()} + ctx.Storage.Set(ApplicationIDQueryParam, 123) + assert.Equal(t, "", GetApplicationID(ctx)) + }) +} + func TestSafeStorage(t *testing.T) { t.Parallel() diff --git a/pkg/rpc/doc.go b/pkg/rpc/doc.go index 2c0683da4..1e972f715 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://clearnode-sandbox.yellow.org/v1/ws", func(err error) { +// err := client.Start(ctx, "wss://nitronode-sandbox.yellow.org/v1/ws", func(err error) { // if err != nil { // log.Error("Connection closed", "error", err) // } diff --git a/pkg/rpc/methods.go b/pkg/rpc/methods.go index de8a37df6..1d108e024 100644 --- a/pkg/rpc/methods.go +++ b/pkg/rpc/methods.go @@ -12,7 +12,6 @@ const ( ChannelsV1GetEscrowChannelMethod Method = "channels.v1.get_escrow_channel" ChannelsV1GetChannelsMethod Method = "channels.v1.get_channels" ChannelsV1GetLatestStateMethod Method = "channels.v1.get_latest_state" - ChannelsV1GetStatesMethod Method = "channels.v1.get_states" ChannelsV1RequestCreationMethod Method = "channels.v1.request_creation" ChannelsV1SubmitStateMethod Method = "channels.v1.submit_state" ChannelsV1SubmitSessionKeyStateMethod Method = "channels.v1.submit_session_key_state" diff --git a/pkg/rpc/node.go b/pkg/rpc/node.go index 10202c559..bb15b86ef 100644 --- a/pkg/rpc/node.go +++ b/pkg/rpc/node.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/layer-3/nitrolite/pkg/app" "github.com/layer-3/nitrolite/pkg/log" ) @@ -55,6 +56,11 @@ type Node interface { // Groups can have their own middleware and can be nested to create // hierarchical handler structures. NewGroup(name string) HandlerGroup + + // RegisteredMethods returns every RPC method name registered on the node + // (across the root group and all subgroups). Intended for instrumentation + // at startup, not for request routing. + RegisteredMethods() []string } type HandlerGroup interface { @@ -131,6 +137,14 @@ type WebsocketNodeConfig struct { WsConnWriteBufferSize int // WsConnProcessBufferSize is the capacity of each connection's incoming message queue (default: 10). WsConnProcessBufferSize int + // WsConnMaxMessageSize caps inbound WebSocket frame size in bytes per connection + // (default: 128 KiB). Frames exceeding this trigger close 1009 before allocation. + // Non-positive values fall back to the default; the cap cannot be disabled at + // this layer. + WsConnMaxMessageSize int64 + // NewFrameRateLimiter constructs a per-connection FrameRateLimiter on each + // upgrade. nil → no enforcement (NoopFrameRateLimiter is used). + NewFrameRateLimiter func() FrameRateLimiter } // NewWebsocketNode creates a new WebsocketNode instance with the provided configuration. @@ -148,7 +162,7 @@ func NewWebsocketNode(config WebsocketNodeConfig) (*WebsocketNode, error) { if config.ObserveConnections == nil { // Default implementation does nothing, but can be overridden for monitoring - config.ObserveConnections = func(region, origin string, count uint32) {} + config.ObserveConnections = func(applicationID string, count uint32) {} } if config.WsUpgraderReadBufferSize <= 0 { // It's the optimal default value as recommended @@ -202,6 +216,14 @@ func NewWebsocketNode(config WebsocketNodeConfig) (*WebsocketNode, error) { // The method ensures proper cleanup when connections close, including // removing the connection from the hub and invoking disconnect callbacks. func (wn *WebsocketNode) ServeHTTP(w http.ResponseWriter, r *http.Request) { + applicationID := r.URL.Query().Get(ApplicationIDQueryParam) + if applicationID != "" && !app.IsValidApplicationID(applicationID) { + wn.cfg.Logger.Warn("rejecting connection with invalid application_id", + "remoteAddr", r.RemoteAddr, "applicationID", applicationID) + http.Error(w, fmt.Sprintf("invalid %s: must match %s", ApplicationIDQueryParam, app.ApplicationIDRegex.String()), http.StatusBadRequest) + return + } + wsConnection, err := wn.upgrader.Upgrade(w, r, nil) if err != nil { wn.cfg.Logger.Error("failed to upgrade connection to WebSocket", "error", err) @@ -211,13 +233,21 @@ func (wn *WebsocketNode) ServeHTTP(w http.ResponseWriter, r *http.Request) { connectionID := uuid.NewString() + var limiter FrameRateLimiter + if wn.cfg.NewFrameRateLimiter != nil { + limiter = wn.cfg.NewFrameRateLimiter() + } + connConfig := WebsocketConnectionConfig{ ConnectionID: connectionID, Origin: r.Header.Get("Origin"), + ApplicationID: applicationID, WebsocketConn: wsConnection, Logger: wn.cfg.Logger, ProcessBufferSize: wn.cfg.WsConnProcessBufferSize, WriteBufferSize: wn.cfg.WsConnWriteBufferSize, + MaxMessageSize: wn.cfg.WsConnMaxMessageSize, + FrameRateLimiter: limiter, } connection, err := NewWebsocketConnection(connConfig) if err != nil { @@ -247,7 +277,7 @@ func (wn *WebsocketNode) ServeHTTP(w http.ResponseWriter, r *http.Request) { } go connection.Serve(parentCtx, childHandleClosure) - go wn.processRequests(connection, parentCtx, childHandleClosure) + go wn.processRequests(connection, applicationID, parentCtx, childHandleClosure) wg.Wait() } @@ -264,9 +294,12 @@ func (wn *WebsocketNode) ServeHTTP(w http.ResponseWriter, r *http.Request) { // The method runs until the connection closes or the context is cancelled. // Each connection has its own SafeStorage instance for maintaining state // across requests. -func (wn *WebsocketNode) processRequests(conn Connection, parentCtx context.Context, handleClosure func(error)) { +func (wn *WebsocketNode) processRequests(conn Connection, applicationID string, parentCtx context.Context, handleClosure func(error)) { defer handleClosure(nil) // Stop other goroutines when done safeStorage := NewSafeStorage() + if applicationID != "" { + safeStorage.Set(ApplicationIDQueryParam, applicationID) + } for { var messageBytes []byte @@ -367,6 +400,17 @@ func (wn *WebsocketNode) Handle(method string, handler Handler) { wn.routes[method] = []string{wn.groupId, method} } +// RegisteredMethods returns every method registered on the node. Order is not +// guaranteed. The underlying map is not mutex-protected, so the call must happen +// after registration has finished (registration is expected to be single-threaded). +func (wn *WebsocketNode) RegisteredMethods() []string { + methods := make([]string, 0, len(wn.routes)) + for method := range wn.routes { + methods = append(methods, method) + } + return methods +} + // handle is the internal method for registering handlers. // It validates inputs and stores the handler in the handler chain. func (wn *WebsocketNode) handle(method string, handler Handler) { diff --git a/pkg/rpc/node_test.go b/pkg/rpc/node_test.go new file mode 100644 index 000000000..a52e36597 --- /dev/null +++ b/pkg/rpc/node_test.go @@ -0,0 +1,58 @@ +package rpc + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/log" +) + +func newTestNode(t *testing.T) *WebsocketNode { + t.Helper() + node, err := NewWebsocketNode(WebsocketNodeConfig{Logger: log.NewNoopLogger()}) + require.NoError(t, err) + return node +} + +func TestRegisteredMethods_EmptyNode(t *testing.T) { + node := newTestNode(t) + assert.Empty(t, node.RegisteredMethods()) +} + +func TestRegisteredMethods_RootHandle(t *testing.T) { + node := newTestNode(t) + noop := func(c *Context) {} + + node.Handle("ping", noop) + node.Handle("status", noop) + + got := node.RegisteredMethods() + sort.Strings(got) + assert.Equal(t, []string{"ping", "status"}, got) +} + +// Group-level Handle must also surface through RegisteredMethods(); the metrics +// seeder relies on a complete list across the root group and all subgroups. +func TestRegisteredMethods_IncludesGroupRegistrations(t *testing.T) { + node := newTestNode(t) + noop := func(c *Context) {} + + node.Handle("ping", noop) + + auth := node.NewGroup("auth") + auth.Handle("auth.login", noop) + auth.Handle("auth.logout", noop) + + nested := auth.NewGroup("admin") + nested.Handle("auth.admin.kick", noop) + + got := node.RegisteredMethods() + sort.Strings(got) + assert.Equal(t, + []string{"auth.admin.kick", "auth.login", "auth.logout", "ping"}, + got, + ) +} diff --git a/pkg/rpc/rate_limiter.go b/pkg/rpc/rate_limiter.go new file mode 100644 index 000000000..dae245d0a --- /dev/null +++ b/pkg/rpc/rate_limiter.go @@ -0,0 +1,70 @@ +package rpc + +import "time" + +// FrameRateLimiter decides whether an inbound WebSocket frame is admitted. +// Implementations attached to a single connection may assume serial access +// from the connection's read goroutine; implementations shared across +// connections must be safe for concurrent use. +// +// Returning false causes the connection to close. +// +// Allocation note: Admit runs after the frame has been read off the wire and +// allocated on the Go heap. Per-frame size is bounded by SetReadLimit (see +// WebsocketConnectionConfig.MaxMessageSize), but a burst of N back-to-back +// max-sized frames can briefly hold up to N * MaxMessageSize bytes per +// connection before this hook closes it. Burst capacity should be sized with +// that ceiling in mind. +type FrameRateLimiter interface { + // Admit reports whether a frame of size bytes is permitted at now. + Admit(now time.Time, size int) bool +} + +// NoopFrameRateLimiter accepts every frame. Default when no limiter is +// configured; useful for tests and dev environments. +type NoopFrameRateLimiter struct{} + +// Admit always returns true. +func (NoopFrameRateLimiter) Admit(time.Time, int) bool { return true } + +// ByteTokenBucket is a token bucket on bytes read. One bucket per connection. +// Not safe for concurrent use; the connection's read goroutine is the sole +// caller of Admit. +type ByteTokenBucket struct { + bytesPerSec float64 + burst float64 + tokens float64 + last time.Time +} + +// NewByteTokenBucket returns a bucket pre-filled to burst capacity. +// +// bytesPerSec is the steady-state refill rate in bytes per second. +// burst is the maximum bucket size; a single frame larger than burst is +// always rejected. +func NewByteTokenBucket(bytesPerSec, burst float64) *ByteTokenBucket { + return &ByteTokenBucket{ + bytesPerSec: bytesPerSec, + burst: burst, + tokens: burst, + } +} + +// Admit refills tokens for elapsed time, caps at burst, then debits size. +// Returns false if size exceeds available tokens. +func (b *ByteTokenBucket) Admit(now time.Time, size int) bool { + if !b.last.IsZero() { + b.tokens += now.Sub(b.last).Seconds() * b.bytesPerSec + if b.tokens > b.burst { + b.tokens = b.burst + } + } + b.last = now + + cost := float64(size) + if b.tokens < cost { + return false + } + b.tokens -= cost + return true +} diff --git a/pkg/rpc/rate_limiter_test.go b/pkg/rpc/rate_limiter_test.go new file mode 100644 index 000000000..cc5b3e2fd --- /dev/null +++ b/pkg/rpc/rate_limiter_test.go @@ -0,0 +1,72 @@ +package rpc_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func TestByteTokenBucket_BurstThenEmpty(t *testing.T) { + t.Parallel() + + b := rpc.NewByteTokenBucket(1024, 4096) + base := time.Unix(0, 0) + + require.True(t, b.Admit(base, 4096), "full burst admitted") + require.False(t, b.Admit(base, 1), "empty bucket rejects") +} + +func TestByteTokenBucket_Refill(t *testing.T) { + t.Parallel() + + b := rpc.NewByteTokenBucket(1024, 4096) + base := time.Unix(0, 0) + + require.True(t, b.Admit(base, 4096)) + require.False(t, b.Admit(base, 1)) + require.True(t, b.Admit(base.Add(time.Second), 1024), "1s of refill admits 1024 bytes") + require.False(t, b.Admit(base.Add(time.Second), 1), "bucket emptied again") +} + +func TestByteTokenBucket_BurstCap(t *testing.T) { + t.Parallel() + + b := rpc.NewByteTokenBucket(1024, 4096) + base := time.Unix(0, 0) + + // Long idle must not let tokens grow past burst. + require.True(t, b.Admit(base.Add(time.Hour), 4096)) + require.False(t, b.Admit(base.Add(time.Hour), 1)) +} + +func TestByteTokenBucket_FrameLargerThanBurstRejected(t *testing.T) { + t.Parallel() + + b := rpc.NewByteTokenBucket(1024, 4096) + require.False(t, b.Admit(time.Unix(0, 0), 4097), + "frame larger than burst is always rejected") +} + +func TestByteTokenBucket_PartialRefill(t *testing.T) { + t.Parallel() + + b := rpc.NewByteTokenBucket(1000, 1000) + base := time.Unix(0, 0) + + require.True(t, b.Admit(base, 1000)) + require.False(t, b.Admit(base.Add(500*time.Millisecond), 501), + "500ms refills 500 bytes, not enough for 501") + require.True(t, b.Admit(base.Add(500*time.Millisecond), 500), + "500ms refills exactly 500 bytes") +} + +func TestNoopFrameRateLimiter_AdmitsAll(t *testing.T) { + t.Parallel() + + var lim rpc.FrameRateLimiter = rpc.NoopFrameRateLimiter{} + require.True(t, lim.Admit(time.Now(), 1<<30)) + require.True(t, lim.Admit(time.Time{}, 0)) +} diff --git a/pkg/rpc/types.go b/pkg/rpc/types.go index 31826af3f..0e309824a 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/types.go @@ -67,10 +67,14 @@ type ChannelSessionKeyStateV1 struct { Version string `json:"version"` // Assets associated with this session key Assets []string `json:"assets"` - // Expiration time as unix timestamp of this session key + // ExpiresAt is the unix timestamp (seconds) at which the session key stops + // authorizing requests. A future value activates the key; a value at or before + // the current time revokes it immediately. Negative values are rejected. ExpiresAt string `json:"expires_at"` // UserSig is the user's signature over the session key metadata to authorize the registration/update of the session key UserSig string `json:"user_sig"` + // SessionKeySig is the session-key holder's signature proving possession of the key being registered. + SessionKeySig string `json:"session_key_sig"` } // ============================================================================ @@ -210,10 +214,14 @@ type AppSessionKeyStateV1 struct { ApplicationIDs []string `json:"application_ids"` // AppSessionID is the application session IDs associated with this session key AppSessionIDs []string `json:"app_session_ids"` - // ExpiresAt is Unix timestamp in seconds indicating when the session key expires + // ExpiresAt is the unix timestamp (seconds) at which the session key stops + // authorizing requests. A future value activates the key; a value at or before + // the current time revokes it immediately. Negative values are rejected. ExpiresAt string `json:"expires_at"` // UserSig is the user's signature over the session key metadata to authorize the registration/update of the session key UserSig string `json:"user_sig"` + // SessionKeySig is the session-key holder's signature proving possession of the key being registered. + SessionKeySig string `json:"session_key_sig"` } // ============================================================================ @@ -297,6 +305,8 @@ type BalanceEntryV1 struct { Asset string `json:"asset"` // Amount is the balance amount Amount string `json:"amount"` + // Enforced is the on-chain enforced balance + Enforced string `json:"enforced"` } // TransactionV1 represents a transaction record. @@ -362,3 +372,8 @@ type PaginationMetadataV1 struct { // PageCount is the total number of pages PageCount uint32 `json:"page_count"` } + +// GetLastKeyStatesPageLimit is the API contract for the channels.v1 / app_sessions.v1 +// get_last_key_states endpoints: both the default and the maximum page size are 10. +// Defined in pkg/rpc so the two handler packages share a single source of truth. +const GetLastKeyStatesPageLimit uint32 = 10 diff --git a/pkg/sign/eth_msg_signer.go b/pkg/sign/eth_msg_signer.go index 8b376f07d..7f4fe1779 100644 --- a/pkg/sign/eth_msg_signer.go +++ b/pkg/sign/eth_msg_signer.go @@ -20,8 +20,8 @@ func (s *EthereumMsgSigner) Sign(hash []byte) (Signature, error) { return Signature(sig), nil } -// NewEthereumRawSigner creates a new Ethereum signer from a hex-encoded private key. -func NewEthereumMsgSigner(privateKeyHex string) (Signer, error) { +// NewEthereumMsgSigner creates a new Ethereum signer from a hex-encoded private key. +func NewEthereumMsgSigner(privateKeyHex string) (*EthereumMsgSigner, error) { signer, err := NewEthereumRawSigner(privateKeyHex) if err != nil { return nil, err @@ -30,8 +30,8 @@ func NewEthereumMsgSigner(privateKeyHex string) (Signer, error) { return NewEthereumMsgSignerFromRaw(signer) } -// NewEthereumRawSignerFronRaw creates a new Ethereum signer from an existing Signer instance. -func NewEthereumMsgSignerFromRaw(signer Signer) (Signer, error) { +// NewEthereumMsgSignerFromRaw creates a new Ethereum signer from an existing Signer instance. +func NewEthereumMsgSignerFromRaw(signer Signer) (*EthereumMsgSigner, error) { return &EthereumMsgSigner{ signer, }, nil diff --git a/pkg/sign/kms/gcp/README.md b/pkg/sign/kms/gcp/README.md index e65e53a40..32d4318ee 100644 --- a/pkg/sign/kms/gcp/README.md +++ b/pkg/sign/kms/gcp/README.md @@ -10,7 +10,7 @@ This package provides a `sign.Signer` implementation backed by Google Cloud KMS. # Set your project and location PROJECT_ID="your-project-id" LOCATION="us-east1" -KEY_RING="clearnode" +KEY_RING="nitronode" KEY_NAME="signer" # Create key ring @@ -54,12 +54,12 @@ gcloud kms keys versions get-public-key 1 \ --output-file /tmp/kms-pub.pem # The Ethereum address can be derived from the public key using standard tools. -# Clearnode will log the address on startup. +# Nitronode will log the address on startup. ``` ## IAM Permissions -The service account running clearnode needs these permissions on the KMS key: +The service account running nitronode needs these permissions on the KMS key: - `cloudkms.cryptoKeyVersions.useToSign` — to sign data - `cloudkms.cryptoKeyVersions.viewPublicKey` — to fetch the public key at startup @@ -67,7 +67,7 @@ The service account running clearnode needs these permissions on the KMS key: You can grant these with the predefined role: ```bash -SERVICE_ACCOUNT="clearnode-sa@${PROJECT_ID}.iam.gserviceaccount.com" +SERVICE_ACCOUNT="nitronode-sa@${PROJECT_ID}.iam.gserviceaccount.com" gcloud kms keys add-iam-policy-binding $KEY_NAME \ --keyring $KEY_RING \ @@ -77,16 +77,16 @@ gcloud kms keys add-iam-policy-binding $KEY_NAME \ --role "roles/cloudkms.signerVerifier" ``` -## Clearnode Configuration +## Nitronode Configuration Set these environment variables: ```bash # Use GCP KMS instead of a raw private key -CLEARNODE_SIGNER_TYPE=gcp-kms +NITRONODE_SIGNER_TYPE=gcp-kms # Full key version resource name -CLEARNODE_GCP_KMS_KEY_NAME=projects/my-project/locations/us-east1/keyRings/clearnode/cryptoKeys/signer/cryptoKeyVersions/1 +NITRONODE_GCP_KMS_KEY_NAME=projects/my-project/locations/us-east1/keyRings/nitronode/cryptoKeys/signer/cryptoKeyVersions/1 ``` When running on GKE with Workload Identity, no additional credential configuration is needed. For other environments, set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account key file. @@ -96,8 +96,8 @@ When running on GKE with Workload Identity, no additional credential configurati ```yaml config: extraEnvs: - CLEARNODE_SIGNER_TYPE: gcp-kms - CLEARNODE_GCP_KMS_KEY_NAME: projects/my-project/locations/us-east1/keyRings/clearnode/cryptoKeys/signer/cryptoKeyVersions/1 + NITRONODE_SIGNER_TYPE: gcp-kms + NITRONODE_GCP_KMS_KEY_NAME: projects/my-project/locations/us-east1/keyRings/nitronode/cryptoKeys/signer/cryptoKeyVersions/1 ``` ## Key Requirements diff --git a/pkg/sign/kms/gcp/gcp_test.go b/pkg/sign/kms/gcp/gcp_test.go index 4606e4a19..27d14e23d 100644 --- a/pkg/sign/kms/gcp/gcp_test.go +++ b/pkg/sign/kms/gcp/gcp_test.go @@ -107,7 +107,7 @@ func TestIntegration_RawSignAndRecover(t *testing.T) { // TestIntegration_MsgSignAndRecover wraps the KMS signer in EthereumMsgSigner // (EIP-191) and verifies the signature recovers correctly using the matching -// EthereumMsgAddressRecoverer. This is the same path clearnode uses for state signing. +// EthereumMsgAddressRecoverer. This is the same path nitronode uses for state signing. func TestIntegration_MsgSignAndRecover(t *testing.T) { keyName := skipIfNoKMS(t) @@ -118,7 +118,7 @@ func TestIntegration_MsgSignAndRecover(t *testing.T) { require.NoError(t, err) defer kmsSigner.Close() - // Wrap in EthereumMsgSigner — same as clearnode does for StateSigner + // Wrap in EthereumMsgSigner — same as nitronode does for StateSigner msgSigner, err := sign.NewEthereumMsgSignerFromRaw(kmsSigner) require.NoError(t, err) @@ -240,7 +240,7 @@ func TestIntegration_KMSvsLocalSigner(t *testing.T) { }) t.Run("msg signer comparison (EIP-191)", func(t *testing.T) { - // Wrap both in EthereumMsgSigner — the clearnode StateSigner path + // Wrap both in EthereumMsgSigner — the nitronode StateSigner path kmsMsgSigner, err := sign.NewEthereumMsgSignerFromRaw(kmsSigner) require.NoError(t, err) diff --git a/playground/.claude/CLAUDE.md b/playground/.claude/CLAUDE.md new file mode 100644 index 000000000..593038f0d --- /dev/null +++ b/playground/.claude/CLAUDE.md @@ -0,0 +1,13 @@ +# Playground — Claude Instructions + +## Component & structure reference + +Before making any UI or structural changes, read `REFERENCE.md` in the playground root. It is the authoritative map of: +- Page layout and overlay conditions +- What each component is responsible for (conceptually) +- What each hook owns +- Key user flows + +## Keeping the reference current + +After implementing any change the user requests — new component, removed component, renamed hook, new user flow, changed responsibility — update `REFERENCE.md` to reflect the new state. Update only the sections that changed; do not rewrite unaffected sections. diff --git a/playground/.env.example b/playground/.env.example new file mode 100644 index 000000000..b12897047 --- /dev/null +++ b/playground/.env.example @@ -0,0 +1 @@ +VITE_NITRONODE_URL=wss://nitronode-sandbox.yellow.org/v1/ws diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 000000000..2783f639b --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +.env +.env.local +.env.*.local +*.log +.DS_Store +.vite +PLAN.md +TASKS.md +TODO.md +layout-options.md diff --git a/playground/Dockerfile b/playground/Dockerfile new file mode 100644 index 000000000..9ce853f4d --- /dev/null +++ b/playground/Dockerfile @@ -0,0 +1,50 @@ +# syntax=docker/dockerfile:1.7 +# +# Build context is the repository root (matches nitronode + faucet-app). +# A per-Dockerfile ignore file (playground/Dockerfile.dockerignore) opts +# `sdk/ts` back into the context since the root `.dockerignore` excludes it +# for the Go images. + +FROM node:22-alpine AS builder + +WORKDIR /repo + +# Build the TS SDK first. playground/package.json depends on it via +# `file:../sdk/ts`, so the dist tree must exist before `npm ci` runs in +# the playground workspace. +COPY sdk/ts/package.json sdk/ts/package-lock.json ./sdk/ts/ +RUN cd sdk/ts && npm ci --no-audit --no-fund +COPY sdk/ts ./sdk/ts +RUN cd sdk/ts && npm run build:ci + +# Install playground deps. Done after the SDK is built so the `file:` link +# resolves to a populated dist tree. +COPY playground/package.json playground/package-lock.json ./playground/ +RUN cd playground && npm ci --no-audit --no-fund + +# Build the playground bundle. `npm run build` runs `tsc --noEmit && vite build`, +# emitting to playground/dist with base=/v1/playground/. +COPY playground ./playground +RUN cd playground && npm run build + + +# Unprivileged nginx variant — listens on 8080 by default and runs as the +# `nginx` user (UID 101). pid file lives under /tmp, so nothing in the image +# requires root at runtime. +FROM nginxinc/nginx-unprivileged:1.27-alpine + +USER root +# envsubst (gettext) is required by the entrypoint to render env.js at start. +RUN apk add --no-cache gettext + +COPY playground/deploy/nginx.conf /etc/nginx/conf.d/default.conf +COPY playground/deploy/env.js.tmpl /env.js.tmpl +COPY playground/deploy/entrypoint.sh /docker-entrypoint.d/40-playground-env.sh +RUN chmod +x /docker-entrypoint.d/40-playground-env.sh + +COPY --from=builder /repo/playground/dist /usr/share/nginx/html/v1/playground +RUN chown -R nginx:nginx /usr/share/nginx/html /docker-entrypoint.d /env.js.tmpl + +USER nginx + +EXPOSE 8080 diff --git a/playground/Dockerfile.dockerignore b/playground/Dockerfile.dockerignore new file mode 100644 index 000000000..0a3b2fcef --- /dev/null +++ b/playground/Dockerfile.dockerignore @@ -0,0 +1,29 @@ +# Per-Dockerfile ignore (BuildKit). Used in place of the repo-root +# .dockerignore when building this Dockerfile, so `sdk/ts` is included in +# the build context. + +.git/ +.github/ +.gitignore +.gitmodules +LICENSE +*.md + +contracts/ +docs/ +nitronode/ +faucet-app/ +cerebro/ +pkg/ +sdk/go/ +sdk/mcp/ +sdk/ts-compat/ +test/ +scripts/ + +**/node_modules/ +**/dist/ +**/.DS_Store +**/.env +**/.env.local +**/*.log diff --git a/playground/README.md b/playground/README.md new file mode 100644 index 000000000..79de7a475 --- /dev/null +++ b/playground/README.md @@ -0,0 +1,79 @@ +# Nitrolite Playground + +Developer-facing UI for Nitrolite state channels. Connects MetaMask to a Nitronode and exposes the SDK's high-level operations: deposit, withdraw, transfer, close, plus a per-channel state inspector (Enforced / Signed / Issued) and optional session-key delegation that skips MetaMask popups for off-chain state co-signing. + +## Quick start + +```bash +# From repo root +cd sdk/ts && npm install && npm run build # build SDK once (playground symlinks to sdk/ts/dist) +cd ../../playground +npm install +cp .env.example .env # edit VITE_NITRONODE_URL if needed +npm run dev # http://localhost:3001 +``` + +A MetaMask install is required. The playground requests an account and listens for `accountsChanged` / `chainChanged`. + +**Important:** `playground/node_modules/@yellow-org/sdk` is a symlink to `sdk/ts`. If the SDK source changes, rerun `cd sdk/ts && npx tsc` (or `npm run build`) to refresh `sdk/ts/dist/`; the playground picks the new build up on the next dev-server reload. + +## Configuration + +Only one env var: + +| Var | Purpose | +|-----|---------| +| `VITE_NITRONODE_URL` | WebSocket URL of the Nitronode (defaults to `wss://nitronode-sandbox.yellow.org/v1/ws`) | + +Supported chains are discovered from the Nitronode's `getConfig()`. Public RPC URLs for each chain are looked up at runtime; override via `src/networks.ts` if a chain is missing or you want a private endpoint. + +## Layout + +| File | Purpose | +|------|---------| +| `src/hooks/useWallet.ts` | MetaMask EIP-1193 lifecycle | +| `src/hooks/useNitrolite.ts` | Nitrolite Client lifecycle, balances, signer selection (wallet vs session key) | +| `src/hooks/useChannelOps.ts` | deposit / withdraw / transfer / close with cancellation on wallet switch | +| `src/hooks/useChannels.ts` | `getChannels()` results | +| `src/hooks/useChannelStates.ts` | per-channel Enforced / Signed / Issued + Acknowledge / Checkpoint actions | +| `src/hooks/useSessionKey.ts` | localStorage-backed session-key state + register/clear | +| `src/sessionKey.ts` | storage + register helper + signer construction | +| `src/components/WalletBar.tsx` | top nav: wallet, chain, node status, session-key chip | +| `src/components/ActionPanel.tsx` | balance + tabbed Deposit / Withdraw / Transfer | +| `src/components/ChannelList.tsx` + `ChannelRow.tsx` + `StateViewer.tsx` | channel display | +| `src/components/PendingReceipts.tsx` | assets received but not yet acknowledged | +| `src/components/UnsupportedChainModal.tsx` | blocking modal when wallet on unsupported chain | +| `src/components/SetHomechainModal.tsx` | first transfer for an asset needs a home chain | +| `src/components/SessionKeyBanner.tsx` + `SessionKeySetupModal.tsx` | session-key onboarding UI | + +## Session keys + +After connecting MetaMask, a banner offers to set up a session key — a temporary key (default 24 h) that signs state updates on your behalf so deposit / withdraw / transfer / acknowledge / checkpoint stop popping MetaMask **for the off-chain co-sign step**. On-chain transactions (the actual deposit / withdraw / checkpoint txs, plus the ERC-20 `approve`) still require MetaMask — a session key cannot sign on-chain calls. + +Key facts: + +- Scope: all currently supported assets (`getAssets()` on connect). +- Storage: `localStorage` under `nitrolite_playground_sk_::`. Per-node, per-wallet. +- Expiry: re-checked on load and once a minute. Expired entries are cleared automatically; the banner returns as "Renew". +- Clear: the `✕` on the SK chip in the wallet bar drops the local key. The node-side delegation remains valid until natural expiry; it just stops being used locally. + +ERC-20 approvals use the "infinite approve" pattern: the first deposit for a token approves `floor((2^256 − 1) / 10^decimals)` human units (≈ MaxUint256 in smallest units), so subsequent deposits skip the approval popup. + +## Not in scope + +- Programmatic SK revoke (submit version+1 with `assets=[]`). Local clear only. +- Per-asset SK scoping (always all assets). +- Transaction history panel. +- App sessions. +- Auto-polling. State is refreshed after each operation and on the refresh button. + +See `playground/TODO.md` (in the original design pack) for the full deferred list. + +## Stack + +- React 19, TypeScript, Vite 5 +- Tailwind 3 with custom CSS variables (see `src/index.css`) +- `@yellow-org/sdk` (file: linked to `sdk/ts`) +- `viem` for wallet + on-chain reads +- `sonner` for toasts +- `lucide-react` for icons diff --git a/playground/REFERENCE.md b/playground/REFERENCE.md new file mode 100644 index 000000000..5a97de3fc --- /dev/null +++ b/playground/REFERENCE.md @@ -0,0 +1,263 @@ +# Playground Reference + +Developer-facing playground for Nitrolite — wallet, channel, and state channel operations. + +**Stack**: React 19, TypeScript, Vite, Tailwind CSS, viem, @yellow-org/sdk, sonner (toasts), lucide-react (icons) + +--- + +## Page Layout + +Single-page app. Sticky header, scrollable body. + +- **Header**: Wallet connection bar (left: brand + node status; center: Main/History/Session Keys tabs when connected; right: SK chip + chain + address + disconnect) +- **Body — Main tab (2-col on desktop)**: + - Left: Action panel (deposit / withdraw / transfer / faucet tabs) + - Right: Channel list (includes incoming unacknowledged channels at the top) +- **Body — History tab (full-width)**: + - Transaction history table with column-filter popovers, per-cell quick filter, expandable row detail, and pagination (25/page) +- **Body — Session Keys tab (full-width)**: + - Table of all session keys (active, expiring, expired, revoked) fetched from the node + - Register, update (renew), and revoke keys via modals + +Tab selector only appears when a wallet is connected. Switching tabs preserves state in both panels. + +**Gating overlays** (modals) appear on top when: +- Wallet is on an unsupported chain +- User sets a home chain for an asset +- User sets up or renews a session key + +--- + +## Components + +### WalletBar +**For**: Wallet identity, chain context, and node health at a glance. +- Connect / disconnect MetaMask +- Display current address and active chain +- Session key chip: shows time remaining, allows clearing the key +- Node status: shows last successful communication timestamp, or error if unreachable +- Tab selector: Main / History / Session Keys (shown when wallet is connected) + +### ActionPanel +**For**: All channel money operations initiated by the user. +- **Deposit tab** — move on-chain funds into a channel (requires MetaMask approval + transaction) +- **Withdraw tab** — move channel funds back on-chain +- **Transfer tab** — send channel funds to another address (requires recipient address input) +- **Faucet tab** — visible only for assets in `FAUCET_ASSETS` (currently YUSD); shows a "Request drip" button that calls the faucet endpoint (mock until endpoint is configured) +- Token selector (custom dropdown via TokenSelector), amount input, Max button (auto-fills from relevant balance) +- Enforces amount limits: cannot exceed on-chain balance (deposit) or channel balance (withdraw/transfer) +- **Cross-chain guard**: if the selected asset has a home channel on a different chain than the wallet's current chain, the deposit/withdraw/transfer form is blurred and a "Select {chain}" button appears to switch chains; the Faucet tab remains accessible regardless of chain state + +### TokenSelector +**For**: Custom asset/token picker used inside ActionPanel. +- Displays each asset as a row: token icon + symbol + supported chain icons +- Assets in `FAUCET_ASSETS` show a small drip icon (Droplets) with a "Faucet available" tooltip next to the symbol +- Token and chain icons are loaded from CDN (see `src/icons.ts`); unknown symbols fall back to a letter avatar +- If a non-closed home channel exists for an asset, all chain icons except the home chain are dimmed (greyscale + low opacity) + +### ChannelList +**For**: Overview of all channels and incoming unacknowledged states for the connected address. +- Refresh button to re-fetch channels from node +- Renders `IncomingChannelRow` for assets that have a balance but no open channel (incoming, unacknowledged) +- Renders a `ChannelRow` per established channel + +### ChannelRow +**For**: Per-channel identity, status, and drill-down into state. +- Displays asset symbol, home chain, truncated channel ID with copy button +- Status badges: "wrong chain" (wallet is on a different chain), "closed" +- Expand/collapse to see channel state detail (StateViewer) or a closed notice +- Inline prompt to switch wallet chain when it doesn't match the channel's home chain +- Close channel button +- Does **not** show a session-key selector — the active SK is wallet-global (not per-channel); manage it via WalletBar chip + Session Keys tab + +### StateViewer +**For**: Inspecting and acting on the three layers of channel state. +- **Enforced** — last checkpoint committed on-chain; lowest trust, highest finality +- **Signed** — both parties have signed; ready to checkpoint on-chain +- **Issued** — node has proposed; needs acknowledgement before it becomes signed +- Each layer shows version number and balance +- **Acknowledge** button: accepts issued state (moves to signed) +- **Checkpoint** button: commits signed state on-chain + +### IncomingChannelRow +**For**: An asset that has an issued (node-proposed) state but no acknowledged channel yet. +- Shows a "NO HOME CHAIN" pill (reddish, low contrast) with tooltip explaining that the wallet's current chain becomes the home chain on acknowledge +- Expands to show the issued state (version + amount) in the same format as StateViewer +- **Acknowledge** button: calls `setHomeBlockchain(asset, currentChainId)` then `acknowledge(asset)`; after success the full channel list refreshes and the row transitions into a regular ChannelRow + +### SessionKeyBanner +**For**: Nudging the user to set up a session key when none is active. +- Appears when wallet is connected but no session key exists +- "Set up" button navigates to the Session Keys tab + +### SessionKeysTab +**For**: Full management of session keys (list, register, update/renew, revoke, reactivate). +- Fetches all session keys (active + inactive) from the node via `useSessionKeyManagement` +- Displays a table with address (truncated + copy), assets, expiration (3-way format toggle: relative / UTC date / Unix), status badge, version, and per-row actions +- "Expiring Soon" banner at the top when any key has < 1 hour remaining +- Register New opens `SessionKeyRegisterForm` in register mode +- Per-row actions depend on status and whether the key's private key is still in localStorage (`hasLocalKey`): + - **active** → Update, Revoke, Use (if not current) + - **expiring** → Renew, Revoke, Use (if not current) + - **expired/revoked + hasLocalKey** → Reactivate (re-registers via update flow at version+1 with future expiry) + - **expired/revoked + !hasLocalKey** → no row actions (cannot update without the private key) +- Revoke opens `SessionKeyRevokeModal` (sets expiresAt to past) +- "IN USE" badge on the currently active key + +### SessionKeySetupModal +**For**: Legacy modal for quick session key creation (still present but no longer wired from the banner). +- Clarifies what a session key is (24h authorization stored locally, avoids MetaMask popups per state op) +- Confirm triggers one MetaMask signature, then session key is stored in localStorage +- Cancel dismisses without changes + +### SetHomechainModal +**For**: Choosing which chain an asset settles on when performing a transfer. +- Appears automatically when a transfer cannot proceed because no home chain is set +- Radio list of eligible chains (those that support the asset) +- Confirm sets the home chain, then the pending transfer is retried automatically + +### UnsupportedChainModal +**For**: Guiding the user off an unsupported chain. +- Appears when the connected wallet's chain is not recognized by the node +- Lists supported chains; clicking one triggers a wallet chain-switch request + +### HistoryTab +**For**: Full-width transaction history view, shown when the History tab is active. +- Fetches up to 200 recent transactions via `client.getTransactions(address, { pageSize: 200 })`; sorts newest-first; paginates at 25/page client-side +- Column header popovers (funnel icon) for Type (multi-select checkboxes), Asset (multi-select), From/To (text input); Apply/Clear buttons in each popover +- Per-cell quick filter: clicking a Type badge, Asset name, or From/To address immediately adds/removes an exact-value filter with tooltip feedback +- Expandable rows: clicking a row reveals Sender new state ID, Receiver new state ID, Timestamp, and a Confirmation timeline (Signed → Co-signed for off-chain; Signed → Broadcasted → Confirmed for on-chain) +- All filtering is client-side after the initial fetch; Refresh button re-fetches from the node + +### CopyButton +**For**: One-click copy of addresses or hashes throughout the UI. +- Checkmark feedback for 1.5s after copy + +--- + +## Hooks + +### useWallet +**Owns**: MetaMask connection lifecycle. +- Wallet address, chain ID, viem WalletClient +- Connect / disconnect / switch chain actions +- Passively detects account and chain changes from MetaMask events + +### useNitrolite +**Owns**: SDK client lifecycle and all node-level data. +- Client creation: probes node for supported chains/assets, then builds final authenticated client +- Session key vs wallet signing: uses session key for state ops if one is active; wallet otherwise +- Channel balances (from node) and on-chain balances (from each chain's RPC) +- Node connection state, error state, last-comms timestamp + +### useChannels +**Owns**: List of channels for the connected address. +- Fetches from node on demand, surfaces loading and error states + +### useChannelStates +**Owns**: The three-layer state (enforced / signed / issued) for one channel. +- Determines whether Acknowledge and Checkpoint actions are available +- Runs acknowledge (sign + upload) and checkpoint (on-chain transaction) operations +- Refreshes balances after each successful operation + +### useChannelOps +**Owns**: High-level channel operations triggered from ActionPanel. +- Deposit: token allowance check → approve if needed → deposit → checkpoint +- Withdraw: withdraw → checkpoint +- Transfer: transfer → if home chain missing, shows modal → retries after chain is set +- Close: close channel → checkpoint +- Tracks operation loading states per action; cancels stale ops on address/wallet change +- Distinguishes user rejections (MetaMask code 4001) from real errors for toast messaging + +### useSessionKey +**Owns**: Session key storage and registration. +- Loads session key from localStorage on address change; also loads `allKeys` (all stored keys) +- Registers a new key: generates keypair, finds next version, signs ownership with wallet, submits to node +- `selectKey(address)` — switches the active signing key among locally stored keys +- Clears key from storage +- Re-checks expiry every 60s so the banner re-appears in the same session if a key expires + +### useSessionKeyManagement +**Owns**: Server-side session key fetching and register/update/revoke operations. +- `fetchKeys()` — fetches all keys (including inactive) from the node via `client.getLastChannelKeyStates` +- `register(client, walletAddress, assets, expiresAt)` — registers a new key, saves to localStorage, returns StoredSessionKey +- `update(client, walletAddress, currentKey, assets, expiresAt)` — re-registers with new assets/expiry at the next version +- `revoke(client, walletAddress, currentKey)` — re-registers with `expiresAt` in the past to invalidate the key +- Exposes `serverKeys`, `isLoading`, `isSubmitting` + +--- + +## Utilities + +### icons.ts +- Static mapping of token symbol → CDN icon URL (CoinGecko), and chain ID → CDN icon URL (llamao.fi) +- Testnets map to their parent mainnet icon +- `tokenIconUrl(symbol)` / `chainIconUrl(chainId)` return `null` for unknown entries; callers render a fallback + +### utils.ts +- `FAUCET_ASSETS` — Set of lowercase asset symbols that have a test faucet (currently `yusd`) +- Address formatting (abbreviated display) +- Balance formatting (thousands separators, 2 decimal places) +- Relative time ("just now", "5m ago", "2h ago") +- Ethereum address validation + +### networks.ts +- Public RPC URL registry per chain ID +- Lookup helper used when building the SDK client + +### sessionKey.ts +- localStorage key scheme: `nitrolite_playground_sk_{nodeUrl}::{walletAddress}` +- Expiry: 24h, with 5-min renewal buffer +- Load / save / clear helpers +- `registerSessionKey` — full registration flow: keypair generation, node version discovery, wallet signature, node submission +- `buildSessionKeyStateSigner` — wraps stored key into SDK StateSigner interface + +### walletSigners.ts +- `WalletStateSigner` — state signing backed by MetaMask (fallback when no session key) +- `WalletTransactionSigner` — on-chain transaction signing backed by MetaMask (always used for on-chain ops) + +--- + +## Key User Flows + +**First visit** +1. "Connect MetaMask" prompt in body → connect → node probes and client builds → channels load + +**Deposit** +1. Select asset + amount → Deposit → MetaMask: approve token spend (once) → MetaMask: deposit transaction → checkpoint written on-chain + +**Withdraw** +1. Select asset + amount → Withdraw → MetaMask: withdraw transaction → checkpoint written on-chain + +**Transfer** +1. Select asset + amount + recipient address → Transfer → if no home chain set, modal appears → confirm chain → transfer proceeds + +**Close channel** +1. In ActionPanel or ChannelRow → Close → MetaMask: close transaction → checkpoint + +**Session key setup** +1. Banner appears → "Set up" → navigates to Session Keys tab → "Register New" → `SessionKeyRegisterForm` → confirm → one MetaMask signature → key stored locally + submitted to node → future state ops (acknowledge, checkpoint) skip MetaMask + +**Session key management** +1. Session Keys tab → table of all keys from node (active, expiring, expired, revoked) +2. Update/Renew key → `SessionKeyRegisterForm` in update mode (new version registered on node) +3. Revoke key → `SessionKeyRevokeModal` confirm → re-registers with expiresAt in the past +4. Switch active key → "Use" button → `selectKey` updates localStorage active flag + SDK switches signer + +**Acknowledge issued state** +1. Expand channel → StateViewer → Acknowledge button (issued row) → signs with session key or wallet → issued becomes signed + +**Checkpoint signed state** +1. Expand channel → StateViewer → Checkpoint button (signed row) → MetaMask: on-chain transaction → signed becomes enforced + +**Accept incoming channel** +1. Channels section → expand IncomingChannelRow → Acknowledge → `setHomeBlockchain` sets wallet chain as home → `acknowledge` co-signs the issued state → channel appears as a regular ChannelRow + +--- + +## Environment + +| Variable | Purpose | +|---|---| +| `VITE_NITRONODE_URL` | WebSocket URL of the Nitronode backend (e.g. `wss://nitronode-sandbox.yellow.org/v1/ws`) | diff --git a/playground/add-session-key-support.md b/playground/add-session-key-support.md new file mode 100644 index 000000000..be13db13a --- /dev/null +++ b/playground/add-session-key-support.md @@ -0,0 +1,831 @@ +# Session Key Management Tab — Implementation Plan + +**Objective**: Add a dedicated "Session Keys" tab to the Nitrolite Playground that allows users to register, update, revoke, and manage channel session keys through a first-class UI, plus integrate session key selection into the Channels tab for per-channel usage. + +--- + +## Section 1: Functionality + +### User-Requested Features +1. **Register session key**: Create a new session key tied to specific assets with a custom expiry duration +2. **Update session key**: Change the assets authorized or extend the expiry of an active key +3. **Revoke session key**: Immediately deactivate the current session key +4. **View session keys**: List all session keys (active and revoked) with their metadata + +### Recommended Enhancements +1. **Status indicators**: Show each key's status (Active, Expired, Revoked, Expiring Soon) +2. **Version tracking**: Display the version number of each registered state for audit clarity +3. **Asset granularity**: Register different keys for different asset subsets (not just "all assets") — user selects assets via checkboxes at registration time +4. **Session key selection per channel** (in Channels tab): Let users choose which session key to use when signing for a specific channel, or fall back to wallet +5. **Auto-renewal prompts**: Warn users when a session key is within 1 hour of expiry and offer renewal +6. **Revocation history**: Maintain a local record of revoked keys with timestamps (for transparency) +7. **Multiple keys per wallet**: Support registering multiple session keys (e.g., for different apps or signing patterns) +8. **Clickable expiration display** (table column): Cycles between three formats on click — human-readable date, time-until-expiry, and raw unix timestamp. Hover shows hint for next format. Same pattern as the "Timestamp" column in HistoryTab. +9. **Copy session key address**: Allow users to copy the session key address for debugging/auditing +10. **Rate limit feedback**: If server rejects registration (e.g., max keys exceeded), show a clear error + +**Rationale for enhancements**: +- **Asset granularity** is important because users may want one restricted key for a mobile app and another full-featured key for desktop. The current banner only registers "all assets," which is fine for v1 but the tab should support subsets. +- **Multiple keys per wallet** aligns with the server's design (version scoping per `(user_address, session_key, kind)` triplet), and it's common in key management UIs (e.g., SSH keys on GitHub). +- **Revocation history** is useful for debugging: "I revoked a key but did my app stop using it?" A local list (cleared on disconnect) answers that. +- **Auto-renewal prompts** reduce friction. Users appreciate a gentle nudge before lockout. +- **Per-channel selection** is a stretch goal but powerful: it lets users e.g. sign transfers with one key and deposits with another (though the v1 UI can start simpler — just a global selector). +- **Clickable expiration** avoids wasting column space on a fixed format and matches the existing HistoryTab UX pattern users already know. + +--- + +## Section 2: UI Description + +### 2.1 Session Keys Tab (New Top-Level Tab) + +**Location**: Parallel to "Main" and "History" tabs in the tab bar at the top of WalletBar (only visible when wallet connected). + +**Tab Name**: "Session Keys" (or "Keys" if space is tight). + +**Layout**: Single card on desktop/mobile, scrollable if many keys. + +#### 2.1.1 Key Management Table/List + +**Container**: `SessionKeysTab.tsx` (new component) + +**Columns** (table on desktop, expandable rows on mobile): +1. **Address**: Truncated session key address with copy button and icon (key icon from lucide) +2. **Assets**: Comma-separated list of asset symbols (e.g. "USDC, ETH"). Truncate/ellipsis if > 3 assets; show full list on hover in a tooltip +3. **Expiration**: Clickable value that cycles through three formats on each click: + - `date` — human-readable UTC date/time: `2026-05-28 10:30:00 UTC` + - `relative` — time until expiry: `23h 15m` (or `expired` if past) + - `unix` — raw unix seconds: `1748424600` + - Hover tooltip shows what the next click will switch to (e.g. `"Show as relative time"`, `"Show as Unix timestamp"`, `"Show as UTC date"`) + - Default format: `relative` + - Format state is global to the tab (one click changes all rows), same pattern as HistoryTab's `tsFormat` state + - Styled with `.ts-toggle` CSS class (already defined in `index.css`): `cursor: pointer`, accent color on hover, tooltip via `data-tip` + CSS `::after` + - `relative` format does **not** live-tick; it is a static snapshot rendered at mount/refresh (no `setInterval`) +4. **Status**: Pill badge (Active, Expired, Revoked, Expiring Soon, Pending). Color-coded (green=Active, gray=Expired, red=Revoked, orange=Expiring Soon, blue=Pending) +5. **Version**: Display the stored version number (e.g. "v1", "v3") for reference +6. **Actions**: Button group: [Update] [Revoke] (grayed out if already expired/revoked) + +**Table styling**: Use the same card/chip styling as ChannelRow. Rows are clickable to expand detail or inline actions. + +**Column filtering**: Each column header has a minimal popover for filtering (e.g. filter by status "Active"), similar to HistoryTab. + +#### 2.1.2 "Register New Key" Button + +**Location**: Top right of card header, next to the "Refresh" button (if a refresh button is added). + +**Label**: "Register New" or "+ New Key" + +**Action**: Opens an inline form or modal (`SessionKeyRegisterForm` component, new). + +#### 2.1.3 Register Form (Inline or Modal) + +**Title**: "Register a new session key" + +**Fields**: +1. **Assets** (required, multi-select): + - Rendered as checkboxes (not a dropdown, so users can pick subsets) + - List all supported assets (e.g. USDC, ETH, YUSD) + - Default: all checked + - Validation: at least one asset must be selected + - Display asset icons and symbols (reuse TokenSelector's asset icon logic if possible) + +2. **Expiration** (required): + - Three input modes, switchable via a small segmented control / tab: **Date**, **Duration**, **Unix** + - **Date mode**: datetime-local input (or date + time two-field). User picks a calendar date and time. Validation: must be in the future. + - **Duration mode**: text input with a unit selector (hours / days). Pre-filled with `24` hours as default. Displays the calculated absolute expiry below the field: `"Expires: 2026-05-28 10:30:00 UTC"`. Validation: result must be > now + 1 minute. + - **Unix mode**: plain number input for a unix timestamp (seconds). Displays the human-readable equivalent below: `"2026-05-28 10:30:00 UTC"`. Validation: must be > now + 60. + - All three modes resolve to the same `expires_at` (unix seconds bigint) sent to the SDK. + - Switching between modes converts the current value where possible (e.g. switching from Duration to Date pre-fills the date with the computed value). + +3. **Summary** (read-only): + - "This key will authorize: [asset list] and expire in [duration]" + - "On-chain operations (deposit, checkpoint, approve) will still require MetaMask" + +**Buttons**: +- Cancel (dismisses form, clears state) +- Register & Sign (disabled until at least one asset is selected and expiry is valid; shows spinner during registration) + +**Flow**: +1. User clicks "Register New" +2. Form opens (modal or inline below the table; modal is less disruptive for busy layouts) +3. User selects assets and expiry +4. User clicks "Register & Sign" +5. SDK is called (via `useNitrolite` client); MetaMask pops up once for wallet signature +6. Key is stored locally (already done via `useSessionKey` and `sessionKey.ts`) +7. Table refreshes and shows the new key +8. Toast: "Session key registered successfully" +9. Form closes + +**Error handling**: +- If registration fails (e.g., max keys exceeded), show error toast with server message +- If user rejects MetaMask, show toast "Cancelled" (already done in `useSessionKey`) +- If network error, show "Registration failed — check your connection" + +#### 2.1.4 Update Flow + +**Trigger**: "Update" button on a key row + +**What can be updated**: +- Assets: remove/add (keep the same private key; submit new version with updated asset list) +- Expiry: extend (set new expiry to future; version increments) + +**UI**: Opens a modal similar to Register Form, but: +- Prefilled with current assets and expiry +- Title: "Update session key" +- Fields are editable +- Shows current version (e.g. "Updating from v2 to v3") +- Buttons: Cancel, Update & Sign +- Behavior is identical to registration (version check, wallet signature, etc.) + +**Post-update**: +- Old key is still usable until the update is submitted; both versions exist momentarily on the server +- Updated key replaces the old one in the table (same session key address, higher version) +- Toast: "Session key updated" + +#### 2.1.5 Revoke Flow + +**Trigger**: "Revoke" button on a key row + +**UI**: Confirmation modal +- Title: "Revoke session key" +- Message: "Are you sure? This key will no longer authorize any operations." +- Address of key being revoked (truncated, copyable) +- Buttons: Cancel, Revoke & Sign + +**Backend**: Same registration flow, but `expires_at` is set to `<= now()` (e.g. current unix time), which tells the server to revoke immediately + +**Post-revoke**: +- Key status changes to "Revoked" +- Buttons disabled +- Toast: "Session key revoked" +- If this was the active key in the global session, it's automatically cleared (see WalletBar integration below) + +#### 2.1.6 Empty State + +**When no keys exist**: +- Centered message: "No session keys yet. Register one to sign state updates without MetaMask prompts." +- Large "Register New" button + +#### 2.1.7 Refresh Button + +**Location**: Card header, right side (next to "Refresh" in ChannelList pattern) + +**Action**: Re-fetch the list of keys from the server via `client.getLastChannelKeyStates(address, undefined, { includeInactive: true })` (already exists in SDK) + +**Behavior**: Shows spinner while loading; updates the table + +**Why needed**: User may revoke a key from another device; refresh brings the UI in sync. + +--- + +### 2.2 Channels Tab Changes + +**Goal**: Let users see and select which session key is active for signing operations in each channel. + +**Approach** (recommended for MVP; can be simplified): +1. **Global session key selector** (simpler): One dropdown in the Channels card header that selects the active session key for all channels + - Options: [No Key] (wallet only), [Active Key], [Other Registered Keys] + - Display: Truncated address of selected key + - If user switches, the SDK client is rebuilt with the new key + +2. **Per-channel selector** (stretch goal, more complex): Each ChannelRow has a small session key picker + - Allows different keys for different channels (e.g. USDC uses Key1, ETH uses Key2) + - Requires storing a mapping in localStorage: `{ channelId -> sessionKeyAddress }` + - Requires changes to `useChannelStates` and `useChannelOps` to pass the per-channel key choice to the signing functions + - More flexibility but increases complexity; recommend for v2 + +**Recommendation for v1**: Implement the **global selector** in Channels card header. It's simpler, aligns with the current WalletBar "SK · 23h" chip design, and covers the main use case. + +**UI** (global selector approach): + +#### 2.2.1 Session Key Selector in ChannelList Header + +**Location**: Channels card header, left side (next to the "Channels" title and count) + +**Rendering**: Dropdown/select similar to the chain selector in WalletBar +- Label: "Key: " or just an icon (key from lucide) +- Selected value: Truncated address of active key, or "Wallet" if none +- Options list: + - "Wallet (no key)" — always first, forces re-build of client with wallet signer only + - Separator + - [List of all non-expired keys, grouped by status] + - Separator + - [List of expired/revoked keys, grayed out] + +**Example**: +``` +Key: 0x1a2B…c3dE + v +[ Wallet (no key) + ────────────── + ✓ 0x1a2B…c3dE (active, expires 23h) + 0x9fEd…A1b2 (expires 2h) ← orange badge "Expiring Soon" + ────────────── + 0xAbCd…1234 (expired) +] +``` + +**On selection change**: +1. Call `sk.selectKey(selectedAddress)` or similar in useSessionKey (new action) +2. Rebuild the SDK Client in `useNitrolite` with the new key (if a key is selected) or wallet signer (if "Wallet" is selected) +3. Clear old key from session state if a different one is selected +4. Toast: "Switched to [address]" or "Using wallet only" + +#### 2.2.2 Visual Indicator + +**In ChannelRow**: Small badge or icon next to the channel name indicating which key is active +- Example: Green key icon if an SK is active, or nothing if wallet only +- Tooltip on hover: "Signed with: [key address] / Wallet" + +**Rationale**: Users should know at a glance whether they're using a key or wallet for a channel. + +--- + +### 2.3 WalletBar / Global Session Key Indicator + +**Current state**: Already has a chip showing active SK with expiry countdown and clear button. + +**Changes**: +1. Update the chip to link to the Session Keys tab (e.g., click the chip to switch to "Session Keys" tab, or add a "Manage" link next to the chip) +2. If a key expires while the tab is active, refresh the chip in real-time (already done via `setInterval` in `useSessionKey`) +3. If a key is revoked from the server (detected on next refresh), clear it from state and show banner again + +**No major changes needed**; the current design is good. + +--- + +### 2.4 Action Panel Changes + +**Current state**: Deposit, withdraw, transfer, faucet tabs; all signing is invisible (handled by `useChannelOps`). + +**Changes**: None required for v1. The Client already uses the active session key (or wallet) transparently. + +**Future enhancement** (v2): +- Show a badge on the Action Panel indicating which key is signing (e.g. "Signing with SK: 0x1a2B…c3dE" or "Signing with wallet") +- Allows users to switch keys mid-operation without re-selecting the asset + +--- + +## Section 3: Implementation Plan + +### 3.1 Files to Create + +#### 1. `src/components/SessionKeysTab.tsx` +**Purpose**: Main tab UI, displays list of session keys and register form. + +**Responsibilities**: +- Fetch list of keys from server on mount (via `client.getLastChannelKeyStates`) +- Render table/list with columns (address, assets, expires, status, version, actions) +- Render "Register New" button and form modal +- Handle register/update/revoke flows +- Manage local revocation history (Set of revoked addresses) for quick visual feedback +- Refresh action + +**State**: +- `keys: ChannelSessionKeyStateV1[]` — list from server +- `showRegisterModal: boolean` — toggle form modal +- `selectedKeyForUpdate: ChannelSessionKeyStateV1 | null` — for update flow +- `isLoading: boolean` — while fetching keys +- `isSubmitting: boolean` — while registering/updating/revoking +- `revokedLocally: Set
` — addresses revoked in this session (cleared on disconnect) + +**Props**: +- `client: Client | null` +- `address: Address | null` +- `sessionKey: StoredSessionKey | null` (from App.tsx) +- `onSessionKeyChanged: (key: StoredSessionKey | null) => void` (triggers client rebuild in App) +- `supportedAssets: Asset[]` + +#### 2. `src/components/SessionKeyRegisterForm.tsx` +**Purpose**: Reusable form for registering and updating session keys. + +**Responsibilities**: +- Render asset checkboxes, expiry picker, summary +- Validate inputs +- Call registration/update flow via `useSessionKey` or new hook +- Handle loading/error states + +**Props**: +- `mode: 'register' | 'update'` +- `initialAssets?: string[]` (for update mode) +- `initialExpirySeconds?: number` (for update mode) +- `supportedAssets: Asset[]` +- `isSubmitting: boolean` +- `onSubmit: (assets: string[], expirySeconds: number) => Promise` +- `onCancel: () => void` + +#### 3. `src/hooks/useSessionKeyManagement.ts` +**Purpose**: High-level session key operations beyond basic registration/clearing. + +**Responsibilities**: +- Fetch list of keys from server +- Handle registration flow (calls existing `useSessionKey.register`) +- Handle update flow (version increment, re-sign, submit) +- Handle revoke flow (version increment, expires_at = now, submit) +- Track loading/error states per operation + +**Interface**: +```typescript +interface UseSessionKeyManagementResult { + keys: ChannelSessionKeyStateV1[]; + isLoading: boolean; + isSubmitting: boolean; + fetchKeys: () => Promise; + register: (assets: string[], expirySeconds: number) => Promise; + update: (key: ChannelSessionKeyStateV1, assets: string[], expirySeconds: number) => Promise; + revoke: (key: ChannelSessionKeyStateV1) => Promise; +} + +function useSessionKeyManagement( + client: Client | null, + address: Address | null, +): UseSessionKeyManagementResult +``` + +--- + +### 3.2 Files to Modify + +#### 1. `src/App.tsx` +**Changes**: +- Add "Session Keys" to `AppTab` type: `type AppTab = 'main' | 'history' | 'keys'` +- Update WalletBar to show the new tab button +- Render `` when `activeTab === 'keys'` +- Pass `onSessionKeyChanged` callback to SessionKeysTab to allow it to select a different key globally + +**Code snippet** (pseudo): +```typescript +type AppTab = 'main' | 'history' | 'keys'; + + + +// In main body: +{activeTab === 'history' ? ( + +) : activeTab === 'keys' ? ( + { + if (key) sk.sessionKey is already set, but allow switching + // Actually, switching keys = selecting different key to use for signing + // This requires a new action in useSessionKey: selectKey(address) + }} + /> +) : ( +
+ + +
+)} +``` + +#### 2. `src/components/WalletBar.tsx` +**Changes**: +- Add "Session Keys" tab button to the center tab selector (only when wallet connected) +- Make the session key chip clickable to navigate to Session Keys tab (optional enhancement) + +**Code snippet** (pseudo): +```typescript + +``` + +#### 3. `src/hooks/useSessionKey.ts` +**Changes**: +- Add new action: `selectKey(sessionKeyAddress: Address) -> void` + - Finds the stored key by address in localStorage + - Sets it as the active session key (triggers re-render) + - Used when user selects a different key from the Channels dropdown + +- Add new action: `getAllStoredKeys() -> StoredSessionKey[]` + - Scans localStorage for all keys matching the node URL and wallet address (e.g., multiple versions of the same key) + - Returns all non-expired keys (or optionally includes expired for the Session Keys tab) + +**Updated interface**: +```typescript +export interface UseSessionKeyResult { + sessionKey: StoredSessionKey | null; + allKeys: StoredSessionKey[]; // NEW: all registered keys for this wallet + isRegistering: boolean; + register: (client: Client, assetSymbols: string[]) => Promise; + selectKey: (sessionKeyAddress: Address) => void; // NEW: switch to a different key + clear: () => void; +} +``` + +#### 4. `src/hooks/useNitrolite.ts` +**Changes**: +- Update Client rebuild logic to watch for changes in the selected session key (already tracks `sessionKey` prop, but may need to support selecting a different key without clearing the old one) +- When `sessionKey` prop changes (or new `selectKey` is called), rebuild the client with the new key + +**No major changes needed**; the existing dependency on `sessionKey` in the useEffect should handle this. + +#### 5. `src/components/ChannelList.tsx` +**Changes**: +- Add session key selector dropdown in the card header (global selector for v1) +- Pass `onSessionKeySelected` callback to SessionKeysTab or accept it as a prop and expose a dropdown + +**Code snippet** (pseudo): +```typescript +
+
+ Channels + ({channels.length}) + + {/* NEW: Session Key Selector */} + +
+ {/* Refresh button */} +
+``` + +#### 6. `src/sessionKey.ts` +**Changes**: +- No major changes. Existing `registerSessionKey` can be reused for registration and updates (the caller just increments the version before calling) +- Add helper function: `getStoredSessionKeys(nodeUrl: string, walletAddress: Address) -> StoredSessionKey[]` + - Returns all non-expired keys from localStorage for this wallet (or add `includeExpired` param) + +#### 7. `src/utils.ts` +**Changes**: +- Add helper: `formatSessionKeyAddress(address: Address, length: number = 4) -> string` + - Returns truncated address like "0x1a2B…c3dE" +- Add helper: `formatExpiryCountdown(expiresAt: number) -> string` + - Returns "23h", "2h 15m", "45m", "15s", "expired" + - Used in SessionKeysTab and WalletBar chip + +#### 8. `REFERENCE.md` +**Changes**: +- Add new section: "### SessionKeysTab" with description of purpose, state, and user flows +- Update "### WalletBar" to mention the Session Keys tab button and global key selector in ChannelList +- Update "## Key User Flows" to add new flow: "Set up multiple session keys" or "Switch between session keys" + +**New entry** (pseudo): +```markdown +### SessionKeysTab +**For**: Registering, managing, and selecting session keys for signing state operations. +- List all registered keys (active, expired, revoked) with assets, expiry, and version +- "Register New" button opens a form to pick assets and expiry duration +- "Update" button allows changing assets or extending expiry (version increment) +- "Revoke" button deactivates a key immediately +- Global session key selector in ChannelList header to choose which key signs for all channels + +### ChannelList Changes +- Added session key selector dropdown (global, for v1) +- Allows switching between active session keys or wallet-only mode +- Rebuilds SDK Client when selection changes +``` + +--- + +### 3.3 New Dependencies / SDK Calls + +**No new npm packages required.** The implementation uses: +- Existing `@yellow-org/sdk` methods: `getLastChannelKeyStates`, `submitChannelSessionKeyState` +- Existing React hooks and Tailwind CSS +- `lucide-react` icons (already in use) + +--- + +### 3.4 State Management Summary + +| State | Owner | Scope | Notes | +|-------|-------|-------|-------| +| Active session key | `useSessionKey` | Wallet + node | Persisted in localStorage; used to build Client | +| All stored keys | `useSessionKey` (new) | Wallet + node | Non-expired keys only, for selector dropdown | +| Keys from server | `useSessionKeyManagement` (new) | Session only | Fetched via `getLastChannelKeyStates`; includes inactive | +| Session key selector choice | `App.tsx` (or `useSessionKey`) | Session only | Which key is actively signing; impacts Client rebuild | +| Register form visibility | `SessionKeysTab` | Component | Local state for modal | +| Revoked locally | `SessionKeysTab` | Session only | Set of addresses revoked in this session for instant UI feedback | + +--- + +### 3.5 Order of Implementation + +**Recommended sequence**: + +1. **Phase 1 — Core UI**: + - Create `SessionKeysTab.tsx` (minimal: list only, no forms) + - Create `useSessionKeyManagement.ts` hook for fetching keys + - Update `App.tsx` to add "Session Keys" tab + - Update `WalletBar.tsx` to show the tab button + - Update `REFERENCE.md` with new components + +2. **Phase 2 — Register/Update/Revoke Forms**: + - Create `SessionKeyRegisterForm.tsx` + - Add form modal to `SessionKeysTab` + - Implement register, update, revoke flows (call SDK methods, handle errors, toasts) + - Test with actual Nitronode + +3. **Phase 3 — Key Selection**: + - Update `useSessionKey.ts` to add `selectKey` and `getAllStoredKeys` actions + - Add session key selector dropdown to `ChannelList` header + - Wire up Client rebuild when key selection changes + - Test switching keys mid-session + +4. **Phase 4 — Polish**: + - Add status badges (Active, Expired, Revoked, Expiring Soon) + - Add column filtering in SessionKeysTab (optional, MVP may skip) + - Add per-channel session key indicator (optional, stretch goal) + - Add auto-renewal prompts when key is < 1 hour from expiry + - Add copy buttons and tooltips + +--- + +## Section 4: Technical Notes + +### 4.1 Version Increment Logic + +When registering, updating, or revoking a key: +1. Call `client.getLastChannelKeyStates(address, undefined, { includeInactive: true })` +2. Find the highest `version` among results for this `(session_key, kind)` pair +3. Increment to `nextVersion = highest + 1` (or `1` if no results) +4. Submit with `version: nextVersion.toString()` +5. If the server rejects with "expected version N, got M", it means a race condition occurred (another tab/device registered a version). Retry step 1-4. + +The `registerSessionKey` function in `sessionKey.ts` already does this; reuse it for update/revoke too. + +### 4.2 Expiry and Renewal + +- **Expiry time**: Unix seconds (bigint) +- **Renewal buffer**: 5 minutes (already in `sessionKey.ts`) +- **Client-side check**: `isExpired(sk)` returns true if `expiresAt - 5min <= now` +- **Server-side check**: `expiresAt > now` required at transaction time +- **Auto-renewal**: When key is < 1 hour from expiry, show a banner or button in SessionKeysTab; clicking "Renew" opens the Update form with the same assets pre-filled and Duration mode defaulting to `24 hours` + +### 4.6 Expiration Column Formatting + +Three-way cycling format, global to the SessionKeysTab component (one `expFmt` state, same pattern as `tsFormat` in HistoryTab): + +```typescript +type ExpFormat = 'relative' | 'date' | 'unix'; + +function formatExpiry(expiresAt: number, fmt: ExpFormat): string { + const now = Math.floor(Date.now() / 1000); + if (fmt === 'unix') return expiresAt.toString(); + if (fmt === 'date') return new Date(expiresAt * 1000).toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; + // relative + const diff = expiresAt - now; + if (diff <= 0) return 'expired'; + const h = Math.floor(diff / 3600); + const m = Math.floor((diff % 3600) / 60); + return h > 0 ? `${h}h ${m}m` : `${m}m`; +} + +const NEXT_FMT: Record = { + relative: 'date', + date: 'unix', + unix: 'relative', +}; + +const FMT_HINT: Record = { + relative: 'Show as UTC date', + date: 'Show as Unix timestamp', + unix: 'Show as relative time', +}; +``` + +The clickable `` uses the existing `.ts-toggle` CSS class from `index.css` (cursor pointer, accent color on hover, tooltip via `data-tip` attribute + `::after` pseudo-element). No new CSS needed. + +### 4.3 localStorage Key Scheme + +Current: `nitrolite_playground_sk_{nodeUrl}::{walletAddress}` (single key per wallet per node) + +For multiple keys: Consider extending to `nitrolite_playground_sk_{nodeUrl}::{walletAddress}::{sessionKeyAddress}` to store each version/address separately. Alternatively, store a JSON array of keys under a single localStorage key. + +**Recommendation**: Use an array approach: `nitrolite_playground_sk_{nodeUrl}::{walletAddress}` = `[{ sessionKeyAddress, privateKey, ... }, ...]`. This way: +- Single localStorage entry per wallet per node +- Easy to iterate and filter +- Existing `loadSessionKey` becomes `loadActiveSessionKey` (returns the one in use); new `loadAllSessionKeys` returns the array + +### 4.4 Error Handling + +**Common errors from server**: +- `invalid_session_key_state: expected version N, got M` → Retry with correct version (race condition) +- `session key does not have permission to sign for this data` → Asset not in registered list; update key +- `max_session_keys_exceeded` → User hit server's per-wallet limit; show error and suggest revoking an old key +- `Token signature invalid` → Shouldn't happen if SDK is correct; check key freshness + +**Client-side validation**: +- Expiry must be in the future (at least 1 minute) +- At least one asset must be selected +- Form disables submit until valid + +### 4.5 Testing Checklist + +- [ ] Register a session key with all assets, 24h expiry +- [ ] Register a second key with subset of assets (e.g., USDC only), 7 days expiry +- [ ] Update the first key to remove an asset +- [ ] Extend the second key's expiry +- [ ] Revoke the first key (status changes to "Revoked", buttons disabled) +- [ ] Switch to the second key via ChannelList dropdown; perform a deposit (should use second key for signing) +- [ ] Switch back to wallet-only; perform a withdraw (should use MetaMask for signing) +- [ ] Let a key expire in real-time; verify status badge updates every second +- [ ] Disconnect and reconnect wallet; verify correct key is loaded from localStorage +- [ ] Manually modify localStorage to corrupt a key; verify app handles it gracefully (clears key, shows error or banner) +- [ ] Revoke a key from another device/session; refresh SessionKeysTab; verify status updates + +--- + +## Section 5: Future Enhancements + +1. **Per-channel session key assignment**: Allow selecting different keys for different channels (requires mapping in localStorage, changes to signing logic) +2. **Cloud backup**: Encrypt and backup session keys to a secure server (requires new backend, key derivation, security review) +3. **Session key templates**: Pre-configured keys (e.g., "Mobile Read-Only: USDC only, 7d expiry") for common patterns +4. **Key rotation automation**: Schedule a key to be rotated/renewed every N days without user intervention +5. **Audit log**: Per-key log of operations signed (requires server support) +6. **Hardware wallet integration**: Support signing session key ownership with hardware wallet (Ledger, Trezor) +7. **Key expiry notifications**: Email/push notif when key expires or is revoked from another device + +--- + +## Summary + +This plan delivers a production-ready "Session Keys" tab that gives users fine-grained control over session key registration, updates, revocation, and selection. The UI is consistent with the existing Nitrolite design, the implementation reuses existing SDK capabilities, and the phased approach allows for MVP launch followed by polish and stretch goals. + +**MVP Scope** (Phase 1 + 2): +- Session Keys tab with list and register form +- Update and revoke flows +- Integration with existing session key storage + +**Nice-to-Have** (Phase 3 + 4): +- Per-channel key selection +- Status badges and auto-renewal prompts +- Multiple keys per wallet support + +--- + +## Section 6: Mockup Specifications + +Mockups live in `mockups/session-keys/`. Each file is a self-contained HTML + CSS document using the exact design tokens, fonts, and class patterns from the live app (`src/index.css`). Use these as the pixel-level reference for implementation. + +--- + +### 6.1 `mockup-1-key-list.html` — Session Keys tab, key list view + +**What it shows**: The steady-state Session Keys tab with four keys in all possible statuses. + +#### WalletBar changes +- "Session Keys" added as the **third tab** in the center tab group: `[Main] [History] [Session Keys]` +- Tab uses the same `.tab` / `.tab.active` pill classes as the existing tabs +- The existing SK chip in the top-right WalletBar (`SK · 23h ×`) remains unchanged; it still shows the active key and expiry + +#### Expiring Soon banner +- Rendered above the card when any registered key is within the warning threshold of expiry +- Layout: orange icon circle (32px, `rgba(249,115,22,0.18)` background with orange key SVG) + text block + "Renew Key" ghost button +- Container: `border-radius: 10px`, `background: var(--warning-dim)` (`rgba(249,115,22,0.12)`), `border: 1px solid rgba(249,115,22,0.25)`, `margin-bottom: 16px` +- Text: title in `#f97316`, subtitle in `var(--text-muted)` naming the key address (truncated mono) and time remaining +- "Renew Key" button: ghost style with `border-color: rgba(249,115,22,0.4)` and `color: #f97316` + +#### Card header +- Left: key SVG icon (muted color) + "Session Keys" `.card-title` + count badge (same rounded pill style as ChannelList — `bg-elevated`, `border`, muted text) +- Right: "Refresh" ghost button (icon + label) + "Register New" primary button (+ icon + label) +- Buttons use `.btn .btn-ghost .btn-sm` and `.btn .btn-primary .btn-sm` respectively + +#### Table columns + +| Column | Header style | Cell content | +|--------|-------------|--------------| +| **Address** | Plain `.th-inner` | Key icon SVG (colored by status) + `JetBrains Mono` 12px truncated address + optional "IN USE" chip (accent-dim background, `font-size:10px`, bold) for the currently active key + copy button (`.copy-btn-addr`, appears on row hover) | +| **Assets** | Plain `.th-inner` | Comma-separated asset symbols in `JetBrains Mono` 12px muted | +| **Expiration** | `.th-inner.filterable` with chevron | `.ts-toggle` span cycling `relative → date → unix` on click; `data-tip` shows next format name. Expired = "expired" in muted; expiring soon = value in `#f97316` | +| **Status** | `.th-inner.filterable` with chevron | Pill badge (see below) | +| **Version** | Plain `.th-inner` | `JetBrains Mono` 12px muted, e.g. "v1", "v3" | +| **Actions** | Right-aligned plain | Button group flush-right (see below) | + +#### Status badges +All use `.badge` base class (inline-flex, `border-radius: 999px`, `padding: 3px 9px`, `font-size: 11px`, `font-weight: 500`), plus a colored dot prefix: + +| Status | Class | Background | Text color | +|--------|-------|------------|------------| +| Active | `.badge-active` | `rgba(34,197,94,0.12)` | `#22c55e` | +| Expiring Soon | `.badge-expiring` | `rgba(249,115,22,0.14)` | `#f97316` | +| Expired | `.badge-expired` | `rgba(102,102,102,0.14)` | `var(--text-muted)` | +| Revoked | `.badge-revoked` | `rgba(239,68,68,0.12)` | `var(--error)` | +| Pending | `.badge-pending` | `rgba(59,130,246,0.12)` | `#3b82f6` | + +#### Action buttons per row + +| Row status | Left button | Right button | +|-----------|-------------|--------------| +| Active | `.btn .btn-ghost .btn-sm` "Update" | `.btn .btn-danger .btn-sm` "Revoke" | +| Expiring Soon | Styled ghost `border-color:rgba(249,115,22,0.4); color:#f97316` "Renew" | `.btn .btn-danger .btn-sm` "Revoke" | +| Expired | `.btn .btn-ghost .btn-sm` disabled | `.btn .btn-danger .btn-sm` disabled | +| Revoked | `.btn .btn-ghost .btn-sm` disabled | `.btn .btn-danger .btn-sm` disabled | + +#### Row opacity +- Active / Expiring Soon: 100% +- Expired: `opacity: 0.7` +- Revoked: `opacity: 0.55` + +#### Row hover +- `tbody tr:hover { background: rgba(255,255,255,0.02); }` + +--- + +### 6.2 `mockup-2-register-modal.html` — Register New Key modal + +**What it shows**: The register modal open over a blurred Session Keys tab, in "Duration" expiry mode with USDC + ETH selected. + +#### Background +- Session Keys tab is rendered at `filter: blur(1.5px); opacity: 0.35; pointer-events: none` to give context without distraction + +#### Overlay +- `.modal-overlay`: `position: fixed; inset: 0; background: rgba(0,0,0,0.72); backdrop-filter: blur(4px); z-index: 50` + +#### Modal card +- `.modal-card` + custom: `width: 440px; max-width: calc(100vw - 32px); padding: 28px; display: flex; flex-direction: column; gap: 20px` +- Sections are separated by full-bleed horizontal hairlines: `height: 1px; background: var(--border); margin: 0 -28px` + +#### Header section (centered) +- 48px circle: `background: var(--accent-dim)` + key SVG in `var(--accent)` +- Title: 17px, `font-weight: 600`, centered +- Subtitle: 13px, `var(--text-muted)`, centered, 1.5 line-height + +#### Assets section +- Section label: 12px, `font-weight: 500`, `var(--text-muted)`, uppercase, `letter-spacing: 0.05em` +- "Select all · Clear" right-aligned in 11px muted, cursor pointer +- Each asset row: flex row, `padding: 10px 14px`, `border-radius: 8px`, `background: var(--bg-elevated)`, `border: 1px solid var(--border)` + - **Unchecked**: default style above + - **Checked**: `border-color: var(--accent); background: var(--accent-dim)` +- Checkbox box: 16px × 16px, `border-radius: 4px`, `border: 2px solid var(--border)`; when checked: `background: var(--accent); border-color: var(--accent)` + white checkmark SVG (stroke-width 3.5) +- Asset icon: 24px circle with brand color background; asset symbol + full name beside it (13px medium + 11px muted) + +#### Expiry section +- Segmented control: outer `background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 3px; gap: 2px` +- Each segment button: `border-radius: 6px; font-size: 12px; font-weight: 500; color: var(--text-muted)`; active = `background: var(--bg-surface); border: 1px solid var(--border); color: var(--text-primary)` +- **Duration mode** (default): number `.input-wrap` (flex, `bg-elevated`, `border`, `border-radius: 8px`) containing plain number input (`JetBrains Mono` 14px) + ` + USDC + + + + + + +
+

Transfer tab preview

+
+
+ +
+ +
+

Invalid Ethereum address

+
+
+ +
+ + USDC +
+
+ +
+ + + +
+
+ Channels (3) + +
+ +
+ + +
+
+ USDC +
+
Ethereum Mainnet
+
0xabc1…f304 + +
+
+ +
+ +
+ + + +
+ + +
+
+ State + Version + Amount + Action +
+ +
+
+ Enforced + + ? + +
+ v12 + 125.00 USDC + +
+ +
+
+ Signed + + ? + +
+ v14 + 115.00 USDC + +
+ +
+
+ Issued + + ? + +
+ v15 + 105.00 USDC + +
+
+
+ + +
+
+ ETH +
+
+ Sepolia + ⚠ Switch to Sepolia +
+
0xdef4…9a21
+
+ +
+
+ + +
+
+ USDC +
+
+ Sepolia + Closed +
+
0x8fa2…c510
+
+
+
+ +
+
+ + + + + + + diff --git a/playground/mockups/general/mockup-c.html b/playground/mockups/general/mockup-c.html new file mode 100644 index 000000000..cf893971d --- /dev/null +++ b/playground/mockups/general/mockup-c.html @@ -0,0 +1,805 @@ + + + + + +Nitrolite Playground + + + + + + + + + + + +
+ + +
+
+ +
+ +
+
0x
+
+
+ 0x742d35Cc...b1c7 + +
+
+ Sepolia +
+
+
+
Disconnect
+
+ +
+ + +
+ +
+
+
+
+
+ Connected +
+
+
Last seen: 2s ago
+
wss://nitronode.yellow.org/ws
+
+
+
+
+ + +
+
+
Balances & Actions
+
+ + + +
+
+ 125.00 + USDC +
+
On-chain: 900.00 USDC
+
+ + +
+
Deposit
+
Withdraw
+
Transfer
+
+ + +
Amount
+
+ + MAX +
+ + + + +
+ + +
Recipient Address
+
+ +
+
⚠ Invalid address format
+ +
Amount
+
+ +
+ + +
+ +
+
+
+ + +
+
+ State Channels + 3 +
+ +
+ + +
+
+
USDC
+
+
USDC
+
Ethereum Mainnet
+
0xabc1...d4e5
+
+
+ +
+
+ +
+ + + +
+ + +
+
+ State + Version + Balance + +
+ + +
+
+
+ Enforced +
+
?
+
+
+
v12
+
125.00 USDC
+
+
+ + +
+
+
+ Signed +
+
?
+
+
+
v14
+
115.00 USDC
+
+ +
+
+ + +
+
+
+ Issued +
+
?
+
+
+
v15
+
105.00 USDC
+
+ +
+
+
+
+ + +
+
+
ETH
+
+
ETH
+
Sepolia Testnet
+
0xdef4...9fa2
+
+
+ +
+
+
+ ⚠ Wallet is on Ethereum — switch to Sepolia to interact +
+
+ + +
+
+
USDC
+
+
USDC
+
Sepolia Testnet
+
0x8fe3...c011
+
+
+ Closed +
+
+
+ +
+
+ +
+ + +
+ +
+ + + diff --git a/playground/mockups/history/a-filter-bar.html b/playground/mockups/history/a-filter-bar.html new file mode 100644 index 000000000..a760c6724 --- /dev/null +++ b/playground/mockups/history/a-filter-bar.html @@ -0,0 +1,477 @@ + + + + + + History — A: Inline Filter Bar + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ Transaction History + 47 transactions +
+ + +
+
+ Type + +
+ +
+ +
+ Asset + +
+ +
+ +
+ Chain + +
+ +
+ +
+ From + +
+ +
+ To + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTypeAssetAmountFromToChainTx Hash
+ 2h ago + Deposit +
+ ETH + ETH +
+
+5.000x1234…Cafe0x9B4F…N0DEEth Sepolia + + 0xabc1…3f4e + + +
+ 45m ago + Transfer +
+ USDT + USDT +
+
100.000x1234…Cafe0x5678…Feed
+ 1h ago + Withdraw +
+ ETH + ETH +
+
-2.500x9B4F…N0DE0x1234…CafeEth Sepolia + + 0xdef2…7a1b + + +
+ 3h ago + Deposit +
+ USDT + USDT +
+
+500.000x1234…Cafe0x9B4F…N0DEBase Sepolia + + 0x789c…2d5f + + +
+ 55m ago + Finalize +
+ ETH + ETH +
+
7.500x1234…Cafe0x1234…CafeEth Sepolia + + 0x012d…8e9a + + +
+
+ + + + +
+
+ + + diff --git a/playground/mockups/history/b-sidebar-drawer.html b/playground/mockups/history/b-sidebar-drawer.html new file mode 100644 index 000000000..dc22cb19c --- /dev/null +++ b/playground/mockups/history/b-sidebar-drawer.html @@ -0,0 +1,541 @@ + + + + + + History — B: Sidebar Drawer Filters + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + +
+
+ Transaction History + +
+ + Type: Deposit + + + + + + Chain: Eth Sepolia + + + + +
+
+ +
+ + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeTypeAssetAmountFromToChainTx Hash
2h agoDeposit
ETHETH
+5.000x1234…Cafe0x9B4F…N0DEEth Sepolia + 0xabc1…3f4e + + +
45m agoTransfer
USDTUSDT
100.000x1234…Cafe0x5678…Feed
1h agoWithdraw
ETHETH
-2.500x9B4F…N0DE0x1234…CafeEth Sepolia + 0xdef2…7a1b + + +
3h agoDeposit
USDTUSDT
+500.000x1234…Cafe0x9B4F…N0DEBase Sepolia + 0x789c…2d5f + + +
55m agoFinalize
ETHETH
7.500x1234…Cafe0x1234…CafeEth Sepolia + 0x012d…8e9a + + +
+
+ + + +
+ + +
+
+ Filters + +
+ +
+ + +
+ Type +
+ + + + +
+
+ + +
+ Asset + +
+ + +
+ Chain + +
+ + +
+ From address + +
+ + +
+ To address + +
+ +
+ + +
+ +
+ +
+
+ + + diff --git a/playground/mockups/history/c-expandable-rows.html b/playground/mockups/history/c-expandable-rows.html new file mode 100644 index 000000000..87e919456 --- /dev/null +++ b/playground/mockups/history/c-expandable-rows.html @@ -0,0 +1,832 @@ + + + + + + History — C: Expandable Rows + Column Filters + + + + + + + + + + +
+ +
+ + +
+ +
+ +
+ Transaction History + 47 transactions · 1 active filter +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Time
+
+
+ Type + +
+
+
Filter by type
+
+
+
+ Deposit +
+
+
+ Withdraw +
+
+
+ Transfer +
+
+
+ Finalize +
+
+
+ + +
+
+
+
+
+ Asset + +
+
Amount
+
+
+ From + +
+
+
Filter by from address
+ +
+ + +
+
+
+
+
+
+ To + +
+
+
+
+ Chain + +
+
Tx Hash
+ + + + just now + + + Deposit + + + +
+ USDT + USDT +
+
+
+500.00 +
+ + 0x1234…Cafe + + +
+
+
+ + 0x9B4F…N0DE + + +
+
+ + Base Sepolia + + +
+ + 0x789c…2d5f + + + Confirming +
+
+ + + + 45m ago + + Transfer + + + +
+ USDT + USDT +
+
+
100.00 +
+ + 0x1234…Cafe + + +
+
+
+ + 0x5678…Feed + + +
+
+
+ + +
+ Sender new state ID + + state_c8f2a4d7e1b39f2a + + +
+ + +
+ Receiver new state ID + + state_a1b2c3d4e5f62a8d + + +
+ + +
+ Timestamp + 2026-05-27 12:47:03 UTC +
+ + +
+ Confirmation +
+
+
+ Signed +
+
+
+
+ Co-signed +
+
+
+ +
+
+ + + + 55m ago + + Finalize + + + +
+ ETH + ETH +
+
+
7.50 +
+ + 0x1234…Cafe + + +
+
+
+ + 0x1234…Cafe + + +
+
+ + Eth Sepolia + + + + 0x012d…8e9a + + +
+ + + + 1h ago + + Withdraw + + + +
+ ETH + ETH +
+
+
-2.50 +
+ + 0x9B4F…N0DE + + +
+
+
+ + 0x1234…Cafe + + +
+
+ + Eth Sepolia + + + + 0xdef2…7a1b + + +
+ + + + 2h ago + + + Deposit + + + +
+ ETH + ETH +
+
+
+5.00 +
+ + 0x1234…Cafe + + +
+
+
+ + 0x9B4F…N0DE + + +
+
+ + Eth Sepolia + + + + 0xabc1…3f4e + + +
+
+ + + +
+
+ + + diff --git a/playground/mockups/history/history-tab.md b/playground/mockups/history/history-tab.md new file mode 100644 index 000000000..61f5f2cee --- /dev/null +++ b/playground/mockups/history/history-tab.md @@ -0,0 +1,215 @@ +# History Tab — Design & Data + +Mockup file: `c-expandable-rows.html` + +--- + +## Layout + +The History tab is a **full-width view** that replaces the two-column (ActionPanel + ChannelList) layout. A tab bar is added at the top of the main content area to switch between **Overview** and **History**. The same WalletBar and outer container are shared with the rest of the app. + +--- + +## Table + +### Columns + +| Column | Content | +|--------|---------| +| *(expand)* | Chevron to expand row detail | +| Time | Relative timestamp (e.g. "2h ago"); absolute UTC on hover | +| Type | Colored badge — see types below | +| Asset | Token icon + symbol | +| Amount | Mono font; `+` green for deposits, `−` red for withdrawals, neutral for transfers/finalize | +| From | Shortened address; copy icon on hover; clickable for quick filter | +| To | Same as From | +| Chain | Compact chain-name pill; clickable for quick filter; `—` for off-chain txs | +| Tx Hash | Shortened hash with block-explorer link (↗); `—` for off-chain txs | + +### Transaction type badges + +| Badge | Color | +|-------|-------| +| Deposit | Green | +| Withdraw | Red | +| Transfer | Blue | +| Finalize | Purple | +| Commit / Release / Rebalance | Muted gray | + +### Sort order + +Newest first (`createdAt` descending). 25 rows per page. Pagination footer shows "Showing 1–25 of N transactions" with Prev / Next buttons. + +### Confirming row (session enrichment) + +Rows deposited or withdrawn in the current session may carry a local **Confirming** badge (accent spinner) while `waitForTransactionReceipt` is pending. This state is ephemeral and lost on reload — see Session Enrichment section below. + +--- + +## Filtering + +Two complementary mechanisms, both updating the same filter state. + +### Column header popovers + +Clicking the funnel icon (▾) on a filterable column opens a popover anchored below that header: + +| Column | Popover content | +|--------|----------------| +| Type | Checkbox list with colored badges (multi-select) | +| Asset | Checkbox list of available asset symbols | +| Chain | Checkbox list of available chain names | +| From | Free-text address input | +| To | Free-text address input | + +Each popover has **Clear** and **Apply** buttons. The column label + icon turn accent-colored when a filter is active on that column. + +### Quick filter (per-cell click) + +Clicking directly on a cell value — a Type badge, Asset name, shortened address, or Chain pill — immediately applies an exact-value filter for that field. If that filter value is already active, the same click removes it. + +Visual feedback: +- Cursor pointer on hover over any filterable cell value +- Subtle highlight background on hover +- Accent-dim background when the filter is active (`.qf-on`) +- Tooltip on hover: **"Quick filter: X"** or **"Remove filter: X"** + +Copy buttons on From / To addresses stop click propagation so they never trigger quick filter. + +--- + +## Expanded Row Detail + +Clicking anywhere on a row (except interactive elements) expands it in-place. A detail panel renders below the row with a 3-column grid: + +| Section | Content | +|---------|---------| +| Sender new state ID | Full state UUID — copy button | +| Receiver new state ID | Full state UUID — copy button | +| Timestamp | Absolute UTC timestamp | +| Confirmation *(spans all 3 cols)* | Step timeline — see below | + +### Confirmation timelines + +**Off-chain (Transfer):** +`Signed ──● Co-signed` + +**On-chain (Deposit / Withdraw / Finalize):** +`Signed ──● Broadcasted ──● Confirmed` + +Completed steps shown in green; pending steps in border color. + +--- + +## Data Fetching + +### Primary source — `client.getTransactions()` + +**Location:** `sdk/ts/src/client.ts` + +```typescript +async getTransactions( + wallet: Address, + options?: { + asset?: string; + txType?: TransactionType; + fromTime?: bigint; // Unix timestamp, seconds + toTime?: bigint; + page?: number; + pageSize?: number; + } +): Promise<{ + transactions: Transaction[]; + metadata: PaginationMetadata; +}> +``` + +Underlying RPC method: `user.v1.get_transactions` + +### `Transaction` type + +```typescript +interface Transaction { + id: string; + asset: string; + txType: TransactionType; + fromAccount: Address; + toAccount: Address; + senderNewStateId?: string; + receiverNewStateId?: string; + amount: Decimal; + createdAt: Date; +} +``` + +### `TransactionType` enum + +```typescript +enum TransactionType { + HomeDeposit = 10, + HomeWithdrawal = 11, + EscrowDeposit = 20, + EscrowWithdraw = 21, + Transfer = 30, + Commit = 40, + Release = 41, + Rebalance = 42, + Migrate = 100, + EscrowLock = 110, + MutualLock = 120, + Finalize = 200, +} +``` + +`Migrate`, `EscrowLock`, and `MutualLock` are not exposed through the API's `tx_type` filter. + +### `PaginationMetadata` type + +```typescript +interface PaginationMetadata { + page: number; // 1-indexed + perPage: number; + totalCount: number; + pageCount: number; +} +``` + +### Which filters are server-side vs. client-side + +| UI filter | Handling | SDK option | +|-----------|----------|-----------| +| Type | **Server-side** | `txType` — single value per call; multi-select needs client-side post-filter | +| Asset | **Server-side** | `asset` | +| Date range | **Server-side** | `fromTime` / `toTime` | +| From address | **Client-side** | No API param; filter on `tx.fromAccount` after fetch | +| To address | **Client-side** | No API param; filter on `tx.toAccount` after fetch | +| Chain | **Client-side** | No API param; chain inferred from channel records | + +For From / To / Chain filtering, either increase `pageSize` to fetch a larger window and filter locally, or fetch all pages (feasible for typical wallet history sizes). + +### Session enrichment + +The server does not store blockchain tx hashes — those are returned by `client.checkpoint()` and confirmed via `waitForTransactionReceipt`. During the current session, `useChannelOps` can maintain an enrichment map: + +```typescript +// Key: senderNewStateId Value: { txHash, status } +type EnrichmentMap = Map; + +// After client.checkpoint(asset) resolves: +enrichment.set(senderNewStateId, { txHash, status: 'confirming' }); + +// After waitForTransactionReceipt resolves: +enrichment.set(senderNewStateId, { txHash, status: 'confirmed' }); +``` + +When rendering the history table, look up each row's `senderNewStateId` in the map to overlay tx hash and confirmation status. This enrichment is lost on page reload, but the tx hash itself can be used to re-query on-chain status if needed. + +### Suggested hook shape + +``` +useHistory(client, address, enrichmentMap) + state: { transactions, metadata, filters, page, isLoading } + fetch: client.getTransactions(address, { asset, txType, fromTime, toTime, page, pageSize: 25 }) + refetch: on address change · filter change · page change · onAfterOp callback + render: merge enrichmentMap by senderNewStateId before passing rows to the table +``` diff --git a/playground/mockups/session-keys/mockup-1-key-list.html b/playground/mockups/session-keys/mockup-1-key-list.html new file mode 100644 index 000000000..eefba8cc5 --- /dev/null +++ b/playground/mockups/session-keys/mockup-1-key-list.html @@ -0,0 +1,632 @@ + + + + + +Session Keys — Key List + + + + + + + + + + + +
+ + +
+
+
+ + + +
+
+
Session key expiring soon
+
+ Key 0x9fEd…A1b2 expires in 45 minutes. Renew to keep signing without MetaMask prompts. +
+
+
+ +
+ + +
+
+
+ + + + Session Keys + 4 +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Address + + Assets + + + Expiration + + + + + + + Status + + + + + + Version + + Actions +
+
+ + + + 0x1a2B…c3dE +
+ + IN USE +
+ +
+
+ USDC, ETH, YUSD + +
+ 23h 15m +
+
● Activev1 +
+ + +
+
+
+ + + + 0x9fEd…A1b2 + +
+
+ USDC + +
+ 45m +
+
● Expiring Soonv3 +
+ + +
+
+
+ + + + 0xAbCd…1234 + +
+
+ ETH + +
+ expired +
+
● Expiredv1 +
+ + +
+
+
+ + + + 0x7eF1…8Ba4 + +
+
+ USDC, ETH + + + ● Revokedv2 +
+ + +
+
+
+ +
+ + diff --git a/playground/mockups/session-keys/mockup-2-register-modal.html b/playground/mockups/session-keys/mockup-2-register-modal.html new file mode 100644 index 000000000..07623f066 --- /dev/null +++ b/playground/mockups/session-keys/mockup-2-register-modal.html @@ -0,0 +1,618 @@ + + + + + +Session Keys — Register Modal + + + + + + + + + + + +
+
+
+
+
+ Session Keys + 2 +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AddressAssetsExpirationStatusVersionActions
0x1a2B…c3dEUSDC, ETH, YUSD23h 15m● Activev1· · ·
0x9fEd…A1b2USDC45m● Expiring Soonv3· · ·
+
+
+
+ + + + + + diff --git a/playground/mockups/session-keys/mockup-3-channels-integration.html b/playground/mockups/session-keys/mockup-3-channels-integration.html new file mode 100644 index 000000000..1ecc0f575 --- /dev/null +++ b/playground/mockups/session-keys/mockup-3-channels-integration.html @@ -0,0 +1,710 @@ + + + + + +Session Keys — Channels Integration + + + + + + + + + + + +
+
+ + +
+
+ + + + +
+ +
+ +
+
Asset
+
+
$
+
+
USDC
+
USD Coin
+
+ + + +
+
+ + +
+
Amount
+
+ + +
+
+ On-chain balance + 1,250.00 USDC +
+
+ + +
+
+ Channel balance + 320.00 USDC +
+
+ + +
+
+ + +
+
+
+
+ Channels + 2 + +
+ + + +
+ + +
+ + + + Wallet (no key) + MetaMask +
+ +
+ + +
+ + 0x1a2B…c3dE + 23h 15m +
+ + + +
+
+ + +
+ + 0x9fEd…A1b2 + 45m ⚠ +
+ +
+ + +
+
+ 0xAbCd…1234 + expired +
+ +
+ + +
+ + + + Manage session keys → +
+
+
+
+ +
+ + +
+ + +
+
+
+
+ 🏦 +
+
+
+ Nitronode + + + + + SK · 0x1a2B…c3dE + +
+
ch_0xDe4f…2e91
+
+
+
+
320.00
+
USDC
+
+
+
+ + +
+
+
+
+ +
+
+
+ App Layer + + + + + Wallet + +
+
ch_0x9bA2…7c03
+
+
+
+
0.50
+
ETH
+
+
+
+ +
+
+ + + + +
+
+ Global session key selector
+ Pick which key signs all channel operations. Switching rebuilds the SDK client instantly — no reconnect needed. +
+
+ + +
+
+ Per-channel indicator
+ Shows which key (or wallet) is actively signing for this channel. Hover for the full address. +
+
+
+ +
+
+ + + diff --git a/playground/package-lock.json b/playground/package-lock.json new file mode 100644 index 000000000..da43c0f70 --- /dev/null +++ b/playground/package-lock.json @@ -0,0 +1,2327 @@ +{ + "name": "nitrolite-playground", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nitrolite-playground", + "version": "0.1.0", + "dependencies": { + "@yellow-org/sdk": "file:../sdk/ts", + "decimal.js": "^10.4.3", + "lucide-react": "^0.469.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "sonner": "^1.7.0", + "viem": "^2.47.4" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript": "^5.6.0", + "vite": "^8.0.14" + } + }, + "../sdk/ts": { + "name": "@yellow-org/sdk", + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "abitype": "^1.2.3", + "decimal.js": "^10.4.3", + "jest-util": "^30.3.0", + "viem": "^2.46.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@ethereumjs/blockchain": "^10.0.0", + "@ethereumjs/common": "^10.0.0", + "@ethereumjs/evm": "^10.0.0", + "@ethereumjs/statemanager": "^10.0.0", + "@ethereumjs/util": "^10.0.0", + "@ethereumjs/vm": "^10.0.0", + "@types/jest": "30.0.0", + "@types/node": "^25.2.3", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", + "eslint": "^10.0.0", + "ethers": "6.16.0", + "glob": "^13.0.3", + "jest": "^30.2.0", + "prettier": "3.8.3", + "rimraf": "^6.1.3", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "^6.0.3", + "ws": "8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@yellow-org/sdk": { + "resolved": "../sdk/ts", + "link": true + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "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.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "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/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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/chokidar/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "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==", + "dev": true, + "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-glob/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/lucide-react": { + "version": "0.469.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", + "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "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/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/ox": { + "version": "0.14.26-386a343.0", + "resolved": "https://pkg.pr.new/ox@386a3439fe1ce76d237930f8c6e6bb493746069a", + "integrity": "sha512-OHHm9re1yVjiMN66GZ2JSGuqmvJPrk40zh3PIS/3I6prZLbt6U/zKlgW18eIIkO0Y/ZyySKr6D/4mUXjBmky1g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "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==", + "dev": true, + "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/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/sonner": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", + "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "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==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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==", + "dev": true, + "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/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.51.0.tgz", + "integrity": "sha512-8C0Ca+eEapXE29vHMUW59NqKENl1X4s9P6xSNC9Nvw6EvAeAhn/LNUlgztk6TOw7KN1Gzz5a/n9Wv4okUfmY9g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "https://pkg.pr.new/ox@386a3439fe1ce76d237930f8c6e6bb493746069a", + "ws": "8.20.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "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 + } + } + } + } +} diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 000000000..78a0b9849 --- /dev/null +++ b/playground/package.json @@ -0,0 +1,32 @@ +{ + "name": "nitrolite-playground", + "version": "0.1.0", + "private": true, + "description": "Developer-facing playground for Nitrolite — wallet, channels, state inspection", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@yellow-org/sdk": "file:../sdk/ts", + "decimal.js": "^10.4.3", + "lucide-react": "^0.469.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "sonner": "^1.7.0", + "viem": "^2.47.4" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript": "^5.6.0", + "vite": "^8.0.14" + } +} diff --git a/playground/postcss.config.js b/playground/postcss.config.js new file mode 100644 index 000000000..2aa7205d4 --- /dev/null +++ b/playground/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/playground/public/env.js b/playground/public/env.js new file mode 100644 index 000000000..84eed7d11 --- /dev/null +++ b/playground/public/env.js @@ -0,0 +1,3 @@ +// Dev-mode placeholder. Production replaces this file at container start via +// envsubst (see playground/deploy/env.js.tmpl + entrypoint). +window.__ENV__ = window.__ENV__ || {}; diff --git a/playground/public/favicon.svg b/playground/public/favicon.svg new file mode 100644 index 000000000..ffa38e8af --- /dev/null +++ b/playground/public/favicon.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/playground/public/icons/chains/base.png b/playground/public/icons/chains/base.png new file mode 100644 index 000000000..2423de63d Binary files /dev/null and b/playground/public/icons/chains/base.png differ diff --git a/playground/public/icons/chains/binance.webp b/playground/public/icons/chains/binance.webp new file mode 100644 index 000000000..383418441 Binary files /dev/null and b/playground/public/icons/chains/binance.webp differ diff --git a/playground/public/icons/chains/ethereum.webp b/playground/public/icons/chains/ethereum.webp new file mode 100644 index 000000000..b7e2c0d5d Binary files /dev/null and b/playground/public/icons/chains/ethereum.webp differ diff --git a/playground/public/icons/chains/linea.webp b/playground/public/icons/chains/linea.webp new file mode 100644 index 000000000..91595014b Binary files /dev/null and b/playground/public/icons/chains/linea.webp differ diff --git a/playground/public/icons/chains/polygon.webp b/playground/public/icons/chains/polygon.webp new file mode 100644 index 000000000..8cf7bd89a Binary files /dev/null and b/playground/public/icons/chains/polygon.webp differ diff --git a/playground/public/icons/chains/xrplevm.png b/playground/public/icons/chains/xrplevm.png new file mode 100644 index 000000000..e78cfebf6 Binary files /dev/null and b/playground/public/icons/chains/xrplevm.png differ diff --git a/playground/public/icons/tokens/bnb.png b/playground/public/icons/tokens/bnb.png new file mode 100644 index 000000000..f2f65af77 Binary files /dev/null and b/playground/public/icons/tokens/bnb.png differ diff --git a/playground/public/icons/tokens/eth.png b/playground/public/icons/tokens/eth.png new file mode 100644 index 000000000..680871d8e Binary files /dev/null and b/playground/public/icons/tokens/eth.png differ diff --git a/playground/public/icons/tokens/matic.png b/playground/public/icons/tokens/matic.png new file mode 100644 index 000000000..ad1c38457 Binary files /dev/null and b/playground/public/icons/tokens/matic.png differ diff --git a/playground/public/icons/tokens/pol.png b/playground/public/icons/tokens/pol.png new file mode 100644 index 000000000..ad1c38457 Binary files /dev/null and b/playground/public/icons/tokens/pol.png differ diff --git a/playground/public/icons/tokens/usdt.png b/playground/public/icons/tokens/usdt.png new file mode 100644 index 000000000..09d5415f8 Binary files /dev/null and b/playground/public/icons/tokens/usdt.png differ diff --git a/playground/public/icons/tokens/xrp.png b/playground/public/icons/tokens/xrp.png new file mode 100644 index 000000000..856d1cfc2 Binary files /dev/null and b/playground/public/icons/tokens/xrp.png differ diff --git a/playground/public/icons/tokens/yellow.png b/playground/public/icons/tokens/yellow.png new file mode 100644 index 000000000..444c2c6ea Binary files /dev/null and b/playground/public/icons/tokens/yellow.png differ diff --git a/playground/session-keys-usage.md b/playground/session-keys-usage.md new file mode 100644 index 000000000..dd25fcd6d --- /dev/null +++ b/playground/session-keys-usage.md @@ -0,0 +1,230 @@ +# Channel Session Keys — Usage Guide + +Source of truth: `pkg/core/session_key.go`, `nitronode/api/channel_v1/`, `sdk/ts/src/signers.ts`, `playground/src/sessionKey.ts`. + +--- + +## Overview + +A channel session key is a delegated signing key. Once registered, it can sign off-chain channel state transitions on behalf of a wallet — no MetaMask popup per state. On-chain operations (deposit, checkpoint, close) always require the wallet. + +--- + +## 1. Issuance (Registration) + +### RPC method +`channels.v1.submit_session_key_state` + +### State object — all fields required at submission + +| Field | Type | Notes | +|-------|------|-------| +| `user_address` | string | Wallet address authorizing the delegation | +| `session_key` | string | Address of the session key being registered | +| `version` | string (uint64) | Must be exactly `latestVersion + 1`; first registration = `"1"` | +| `assets` | string[] | Asset IDs this key may sign for (e.g. `["usdc", "eth"]`) | +| `expires_at` | string (unix secs) | Future → active; past/now → immediate revocation | +| `user_sig` | string | EIP-191 wallet signature (triggers MetaMask once) | +| `session_key_sig` | string | EIP-191 session key ownership proof (local, no popup) | + +### What the signatures sign + +Both signatures sign the same packed payload: + +``` +keccak256(SESSION_KEY_AUTH_TYPEHASH, session_key, metadataHash) +``` + +where: + +``` +metadataHash = keccak256(abi.encode(user_address, version, assets, expires_at)) +``` + +This binds both signatures to the exact `(wallet, session_key, version, assets, expires_at)` tuple — no replay possible across different pairs. + +### TypeScript issuance flow + +```typescript +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import { EthereumMsgSigner, type ChannelSessionKeyStateV1 } from '@yellow-org/sdk'; + +// 1. Generate a fresh throwaway key +const skPrivateKey = generatePrivateKey(); +const skAccount = privateKeyToAccount(skPrivateKey); +const skMsgSigner = new EthereumMsgSigner(skPrivateKey); + +// 2. Build state (version = latestVersion + 1; first time = 1) +const state: ChannelSessionKeyStateV1 = { + user_address: walletAddress, + session_key: skAccount.address, + version: nextVersion.toString(), + assets: ['usdc', 'eth'], + expires_at: (Math.floor(Date.now() / 1000) + 86400).toString(), // 24 h + user_sig: '', + session_key_sig: '', +}; + +// 3. Wallet signs (one MetaMask popup) +state.user_sig = await client.signChannelSessionKeyState(state); + +// 4. Session key signs locally (no popup) +state.session_key_sig = await client.signChannelSessionKeyOwnership(state, skMsgSigner); + +// 5. Submit +await client.submitChannelSessionKeyState(state); +``` + +### What to persist after registration + +You need all of the following to reconstruct the signer in future sessions: + +```typescript +type StoredSessionKey = { + privateKey: Hex; // session key private key + sessionKeyAddress: Address; + walletAddress: Address; + version: string; + assets: string[]; + expiresAt: string; // unix secs + userSig: Hex; // wallet's auth signature +}; +``` + +### Operations: registration / update / revocation + +All three use the same RPC method and the same version-increment rule: + +| Operation | `expires_at` | Effect | +|-----------|-------------|--------| +| Registration | future | Activates the key | +| Update (rotate assets / extend lifetime) | future | Replaces the active state | +| Revocation | `<= now` | Retires the key immediately; slot is freed | + +--- + +## 2. Version Rules + +### The rule +Every submit must have `version == latestVersion + 1` — exactly, not "at least". Any other value returns: +``` +invalid_session_key_state: expected version , got +``` + +### Version 0 is invalid +Version `0` is rejected before any other validation. Internally it is a seed row that reserves ownership of a `(session_key, kind)` slot — never a valid submitted state. + +### Version scoping +Version is scoped per `(user_address, session_key, kind)` triplet. Channel keys (`kind=1`) and app-session keys (`kind=2`) for the same private key have completely independent version sequences. + +### How to get the current version before submitting + +There is no dedicated version endpoint. Call `getLastChannelKeyStates` and read `state.version`: + +```typescript +const states = await client.getLastChannelKeyStates(walletAddress, sessionKeyAddress); + +const nextVersion = states.length === 0 + ? 1n + : BigInt(states[0].version) + 1n; +``` + +### Race conditions +Concurrent submits for the same key are serialized by a row-level lock (`LockSessionKeyState`). The losing transaction re-reads the updated `latestVersion` and receives the "expected version N, got M" error. The client must re-fetch and retry with `latestVersion + 1`. + +--- + +## 3. Using Session Keys for Channel Operations + +### Client initialization + +Pass a `ChannelSessionKeyStateSigner` as `stateSigner` to `Client.create()`. The `txSigner` (on-chain operations) always uses the wallet — never the session key. + +```typescript +import { + Client, + ChannelDefaultSigner, + ChannelSessionKeyStateSigner, + EthereumMsgSigner, + getChannelSessionKeyAuthMetadataHashV1, +} from '@yellow-org/sdk'; + +function buildSessionKeyStateSigner(sk: StoredSessionKey): StateSigner { + const metadataHash = getChannelSessionKeyAuthMetadataHashV1( + sk.walletAddress, + BigInt(sk.version), + sk.assets, + BigInt(sk.expiresAt), + ); + return new ChannelSessionKeyStateSigner( + sk.privateKey, + sk.walletAddress, + metadataHash, + sk.userSig, // wallet's auth signature from registration + ); +} + +// Choose signer based on whether a valid session key exists +const stateSigner = sessionKey + ? buildSessionKeyStateSigner(sessionKey) + : new ChannelDefaultSigner(new WalletStateSigner(walletClient)); + +const client = await Client.create(NODE_URL, stateSigner, txSigner, ...opts); +``` + +### Wire format + +When the client signs with a session key, it does not produce a bare 65-byte ECDSA signature. It produces: + +``` +0x01 || abi.encode( + SessionKeyAuthorization { sessionKey, metadataHash, authSignature }, + sessionKeySig +) +``` + +The `0x01` type byte tells the server to unwrap the authorization bundle. The server then: +1. Verifies `authSignature` (wallet) over `(SESSION_KEY_AUTH_TYPEHASH, sessionKey, metadataHash)` +2. Verifies `sessionKeySig` (session key) over the actual state hash +3. Checks asset permission and expiry + +(`sdk/ts/src/signers.ts:224-255`, `pkg/core/channel_signer.go:172-212`) + +### What can and cannot be session-key signed + +| Operation | Session key OK? | +|-----------|----------------| +| Off-chain state transitions (`submitState`, transfers, acks) | Yes | +| Session key registration (`submitChannelSessionKeyState`) | Partial — `user_sig` must be wallet; `session_key_sig` is the session key | +| On-chain: `checkpoint`, `deposit`, `withdraw`, `closeChannel` | No — always wallet via `txSigner` | + +--- + +## 4. Constraints + +### Asset list +Enforced **server-side only**. The server joins against `channel_session_key_assets_v1` and checks that the asset being signed for is in the registered list. Submitting a state for an asset not in the list returns: +``` +session key does not have permission to sign for this data +``` + +### Expiry +Enforced **server-side only** (`expires_at > now`). The same generic error is returned for both expired keys and wrong-asset cases — intentional, to avoid leaking whether a key exists but is expired. + +The playground uses a client-side `isExpired()` helper with a renewal buffer as a UX optimization, but the SDK itself does not pre-check expiry. + +### `kind` isolation +The same private key can be registered independently as: +- `kind = 1` — channel session key (`channels.v1.submit_session_key_state`) +- `kind = 2` — app-session key (different RPC) + +They have separate version histories and asset lists. The SDK selects `kind` automatically based on which RPC method is called — there is no SDK-level `kind` parameter. + +### One owner per key per kind +Once a wallet seeds the ownership row for a `(session_key, kind)` pair it is permanent. No other wallet can register that session key address for the same kind, even after revocation. + +### Server-side caps (configurable) +- Maximum session keys per user: `maxSessionKeysPerUser` +- Maximum assets per session key: `maxSessionKeyIDs` + +Exceeding either cap at registration returns an error before the version check. diff --git a/playground/src/App.tsx b/playground/src/App.tsx new file mode 100644 index 000000000..8cff8d6a7 --- /dev/null +++ b/playground/src/App.tsx @@ -0,0 +1,186 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Toaster } from 'sonner'; +import { useWallet } from './hooks/useWallet'; +import { useNitrolite } from './hooks/useNitrolite'; +import { useChannels } from './hooks/useChannels'; +import { useChannelOps } from './hooks/useChannelOps'; +import { useSessionKey } from './hooks/useSessionKey'; +import WalletBar from './components/WalletBar'; +import type { AppTab } from './components/WalletBar'; +import ActionPanel from './components/ActionPanel'; +import ChannelList from './components/ChannelList'; +import HistoryTab from './components/HistoryTab'; +import SessionKeysTab from './components/SessionKeysTab'; +import UnsupportedChainModal from './components/UnsupportedChainModal'; +import SetHomechainModal from './components/SetHomechainModal'; + +export default function App() { + const wallet = useWallet(); + const [activeTab, setActiveTab] = useState('main'); + + const sk = useSessionKey(wallet.address); + const nitro = useNitrolite(wallet.address, wallet.walletClient, sk.sessionKey, wallet.chainId); + const channels = useChannels(nitro.client, wallet.address); + + const refreshAll = useCallback(() => { + nitro.refresh(); + channels.refresh(); + }, [nitro, channels]); + + const [channelStatesKey, setChannelStatesKey] = useState(0); + const bumpChannelStates = useCallback(() => setChannelStatesKey(k => k + 1), []); + + const ops = useChannelOps(nitro.client, wallet.address, nitro.supportedAssets, refreshAll, bumpChannelStates); + + const [selectedAsset, setSelectedAsset] = useState(''); + + // Default-select an asset when assets load. + useEffect(() => { + if (!selectedAsset && nitro.supportedAssets.length) { + setSelectedAsset(nitro.supportedAssets[0].symbol); + } + }, [nitro.supportedAssets, selectedAsset]); + + const isChainSupported = useMemo(() => { + if (!wallet.chainId || !nitro.supportedChains.length) return true; + return nitro.supportedChains.some(c => c.id === wallet.chainId); + }, [wallet.chainId, nitro.supportedChains]); + + const showUnsupportedModal = !!wallet.address && !!nitro.isConnected && !isChainSupported; + + return ( +
+ + + + +
+ {!wallet.address ? ( + + ) : nitro.isConnecting ? ( +
Connecting to Nitronode…
+ ) : ( + <> +{activeTab === 'history' ? ( + + ) : activeTab === 'keys' ? ( + a.symbol)} + onKeyActivated={(newSk) => sk.selectKey(newSk.sessionKeyAddress)} + onKeyCleared={sk.clear} + onSelectKey={sk.selectKey} + onRefreshAllKeys={sk.refreshAllKeys} + /> + ) : ( +
+ { refreshAll(); bumpChannelStates(); }} + /> + +
+ +
+
+ )} + + )} +
+ + {showUnsupportedModal && ( + + )} + + {ops.homechainModalAsset && ( + + )} + +
+ ); +} + +function ConnectPrompt() { + return ( +
+

Nitrolite Playground

+

+ Connect a wallet to inspect channels, deposit, withdraw, and transfer assets via the Nitronode. +

+

Use the Connect MetaMask button in the top right.

+
+ ); +} diff --git a/playground/src/chainMeta.ts b/playground/src/chainMeta.ts new file mode 100644 index 000000000..819818614 --- /dev/null +++ b/playground/src/chainMeta.ts @@ -0,0 +1,25 @@ +// Canonical display names and sort order for known chains. +// Used to override the node-provided names in the UI and to order chains consistently. + +export const CHAIN_DISPLAY_NAMES: Record = { + '1': 'Ethereum', + '11155111':'Ethereum Sepolia', + '137': 'Polygon', + '80002': 'Polygon Amoy', + '8453': 'Base', + '84532': 'Base Sepolia', + '42161': 'Arbitrum One', + '421614': 'Arbitrum Sepolia', + '59144': 'Linea', + '59141': 'Linea Sepolia', + '10': 'Optimism', + '56': 'BNB Chain', + '43114': 'Avalanche', + '11235': 'HAQQ', + '1449000': 'XRPL EVM Testnet', +}; + +/** Returns our canonical display name, falling back to the node-provided name. */ +export function chainDisplayName(chainId: bigint, nodeName?: string): string { + return CHAIN_DISPLAY_NAMES[chainId.toString()] ?? nodeName ?? `Chain ${chainId}`; +} diff --git a/playground/src/components/ActionPanel.tsx b/playground/src/components/ActionPanel.tsx new file mode 100644 index 000000000..1e9f78eff --- /dev/null +++ b/playground/src/components/ActionPanel.tsx @@ -0,0 +1,416 @@ +import { useState, useRef, useEffect, useCallback } from 'react'; +import { Decimal } from 'decimal.js'; +import type { Address } from 'viem'; +import type { Asset, Blockchain, Channel } from '@yellow-org/sdk'; +import { ChannelType, ChannelStatus } from '@yellow-org/sdk'; +import { formatBalance, isValidAddress, FAUCET_ASSETS } from '../utils'; +import { chainDisplayName } from '../chainMeta'; +import { showErrorToast } from '../toastError'; +import TokenSelector from './TokenSelector'; +import type { DepositPhase, WithdrawPhase, TransferPhase } from '../hooks/useChannelOps'; +import { FAUCET_URL, FAUCET_ENABLED } from '../config'; + +type Tab = 'deposit' | 'withdraw' | 'transfer' | 'faucet'; +type FaucetPhase = 'idle' | 'requesting' | 'done' | 'rate-limited'; + +interface Props { + assets: Asset[]; + channels: Channel[]; + selectedAsset: string; + onSelectAsset: (asset: string) => void; + balance: Decimal | undefined; + onChainBalance: Decimal | null | undefined; + currentChainId: bigint | null; + chains: Blockchain[]; + onDeposit: (chainId: bigint, asset: string, amount: Decimal) => void; + onWithdraw: (chainId: bigint, asset: string, amount: Decimal) => void; + onTransfer: (to: Address, asset: string, amount: Decimal) => void; + depositPhase: DepositPhase; + withdrawPhase: WithdrawPhase; + transferPhase: TransferPhase; + needsApproval: boolean | null; + checkDepositAllowance: (blockchainId: bigint, asset: string, amount: Decimal) => Promise; + disabled: boolean; + onSwitchChain: (chainId: bigint) => void; + closingAsset: string | null; + address: Address | null; + onRefresh: () => void; +} + +export default function ActionPanel({ + assets, + channels, + selectedAsset, + onSelectAsset, + balance, + onChainBalance, + currentChainId, + chains, + onDeposit, + onWithdraw, + onTransfer, + depositPhase, + withdrawPhase, + transferPhase, + needsApproval, + checkDepositAllowance, + disabled, + onSwitchChain, + closingAsset, + address, + onRefresh, +}: Props) { + const [tab, setTab] = useState('deposit'); + const [amount, setAmount] = useState(''); + const [recipient, setRecipient] = useState(''); + const [recipientError, setRecipientError] = useState(null); + const [faucetPhase, setFaucetPhase] = useState('idle'); + const wasBusyRef = useRef(false); + const operatingTabRef = useRef(null); + + const hasFaucet = FAUCET_ENABLED && FAUCET_ASSETS.has(selectedAsset.toLowerCase()); + + // Pick the chain for deposit/withdraw: current wallet chain if it's supported, else asset's suggested chain. + const asset = assets.find(a => a.symbol === selectedAsset); + const operatingChainId = + asset && currentChainId && asset.tokens.some(t => t.blockchainId === currentChainId) + ? currentChainId + : asset?.suggestedBlockchainId ?? null; + const operatingChain = chains.find(c => c.id === operatingChainId); + const operatingChainName = operatingChain + ? chainDisplayName(operatingChain.id, operatingChain.name) + : undefined; + + const homeChannel = channels.find( + c => + c.asset.toLowerCase() === selectedAsset.toLowerCase() && + c.type === ChannelType.Home && + c.status !== ChannelStatus.Closed, + ); + const homeChainId = homeChannel?.blockchainId ?? null; + const homeChain = homeChainId ? chains.find(c => c.id === homeChainId) : undefined; + const homeChainName = homeChain ? chainDisplayName(homeChain.id, homeChain.name) : null; + const isCrossChain = homeChainId !== null && currentChainId !== homeChainId; + const isChannelClosing = + !!closingAsset && closingAsset.toLowerCase() === selectedAsset.toLowerCase(); + + const channelBalance = balance ?? new Decimal(0); + const amountDecimal = (() => { + try { + return amount ? new Decimal(amount) : new Decimal(0); + } catch { + return new Decimal(0); + } + })(); + const isBusy = + (tab === 'deposit' && depositPhase !== 'idle') || + (tab === 'withdraw' && withdrawPhase !== 'idle') || + (tab === 'transfer' && transferPhase !== 'idle'); + + const amountInvalid = amountDecimal.lte(0); + const amountExceedsChannel = !isBusy && (tab === 'withdraw' || tab === 'transfer') && amountDecimal.gt(channelBalance); + const amountExceedsOnChain = !isBusy && tab === 'deposit' && onChainBalance != null && amountDecimal.gt(onChainBalance); + + const setMax = () => { + if (tab === 'deposit' && onChainBalance) setAmount(onChainBalance.toString()); + else if (tab === 'withdraw' || tab === 'transfer') setAmount(channelBalance.toString()); + }; + + const validateRecipient = () => { + if (!recipient) { + setRecipientError(null); + return false; + } + if (!isValidAddress(recipient)) { + setRecipientError('Invalid Ethereum address'); + return false; + } + setRecipientError(null); + return true; + }; + + const fire = () => { + if (!asset || !operatingChainId) return; + if (tab === 'transfer' && !validateRecipient()) return; + operatingTabRef.current = tab; + if (tab === 'deposit') onDeposit(operatingChainId, selectedAsset, amountDecimal); + else if (tab === 'withdraw') onWithdraw(operatingChainId, selectedAsset, amountDecimal); + else if (tab === 'transfer') onTransfer(recipient as Address, selectedAsset, amountDecimal); + }; + + // Re-check allowance whenever the deposit inputs settle (debounced 400 ms). + useEffect(() => { + if (tab !== 'deposit' || !operatingChainId || amountInvalid) { + return; + } + const t = setTimeout(() => { + checkDepositAllowance(operatingChainId, selectedAsset, amountDecimal); + }, 400); + return () => clearTimeout(t); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tab, operatingChainId, selectedAsset, amountDecimal.toString()]); + + // Clear inputs when the operation finishes (isBusy: true → false). + useEffect(() => { + if (wasBusyRef.current && !isBusy) { + setAmount(''); + if (operatingTabRef.current === 'transfer') { + setRecipient(''); + setRecipientError(null); + } + operatingTabRef.current = null; + } + wasBusyRef.current = isBusy; + }, [isBusy]); + + // Switch away from faucet tab if selected asset no longer has a faucet, or + // if the faucet was disabled at runtime (env-driven, e.g. prod). + useEffect(() => { + if (tab === 'faucet' && (!FAUCET_ENABLED || !FAUCET_ASSETS.has(selectedAsset.toLowerCase()))) { + setTab('deposit'); + } + }, [selectedAsset, tab]); + + const requestDrip = useCallback(async () => { + if (!address) return; + setFaucetPhase('requesting'); + try { + const res = await fetch(FAUCET_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userAddress: address }), + }); + if (!res.ok) { + if (res.status === 429) { + setFaucetPhase('rate-limited'); + } else { + const msg = + res.status === 503 ? 'Faucet service unavailable — try again later' : + 'Faucet request failed'; + showErrorToast(msg); + setFaucetPhase('idle'); + } + return; + } + setFaucetPhase('done'); + onRefresh(); + } catch { + showErrorToast('Faucet request failed — check your connection'); + setFaucetPhase('idle'); + } + }, [address, onRefresh]); + + // For deposit, also require on-chain balance to be loaded before allowing submit. + const depositBalanceReady = tab !== 'deposit' || onChainBalance != null; + + const transferRecipientValid = tab !== 'transfer' || (isValidAddress(recipient) && !recipientError); + const canSubmit = + !disabled && + !isBusy && + !amountInvalid && + !amountExceedsChannel && + !amountExceedsOnChain && + depositBalanceReady && + !!asset && + !!operatingChainId && + transferRecipientValid; + + const amountInputError = amountExceedsChannel || amountExceedsOnChain; + + // Whether the selected asset already has an open channel (affects deposit label). + const channelExists = channels.some( + c => c.asset.toLowerCase() === selectedAsset.toLowerCase() && c.status !== ChannelStatus.Closed, + ); + + const buttonLabel = (() => { + if (tab === 'deposit') { + switch (depositPhase) { + case 'approving': return 'Approving…'; + case 'signing_state': return channelExists ? 'Signing deposit state…' : 'Signing creation state…'; + case 'signing_tx': return 'Signing deposit transaction…'; + case 'confirming': return 'Depositing…'; + default: return needsApproval ? 'Approve' : 'Deposit'; + } + } + if (tab === 'withdraw') { + switch (withdrawPhase) { + case 'signing_state': return 'Signing withdrawal state…'; + case 'signing_tx': return 'Sending withdrawal transaction…'; + case 'confirming': return 'Withdrawing…'; + default: return 'Withdraw'; + } + } + // transfer + return transferPhase === 'signing_state' ? 'Signing transfer state…' : 'Transfer'; + })(); + + return ( +
+
+ Actions +
+ + {/* Balance display — persistent */} +
+ + +
+ Unified balance + {formatBalance(channelBalance)} +
+
+ On-chain + + {onChainBalance == null ? '—' : formatBalance(onChainBalance)} + +
+
+ + {/* Tabs */} +
+ + + + {hasFaucet && ( + + )} +
+ + {/* Deposit / Withdraw / Transfer form — blurred when cross-chain or channel is closing */} + {tab !== 'faucet' && ( +
+
+ {tab === 'transfer' && ( +
+ +
+ { + setRecipient(e.target.value); + if (recipientError) setRecipientError(null); + }} + onBlur={validateRecipient} + /> +
+ {recipientError &&

{recipientError}

} +
+ )} + + +
+ setAmount(e.target.value)} + /> + + {selectedAsset.toUpperCase() || '—'} +
+ + {amountExceedsChannel && ( +

Amount exceeds Unified balance

+ )} + {amountExceedsOnChain && ( +

Amount exceeds on-chain balance

+ )} + + {operatingChainName && tab === 'deposit' && ( +

+ Will deposit on "{operatingChainName}" +

+ )} + + {operatingChainName && tab === 'withdraw' && ( +

+ Will withdraw to "{operatingChainName}" +

+ )} + + +
+ + {/* Cross-chain overlay */} + {isCrossChain && !isChannelClosing && ( +
+

+ Cross-chain operations are not yet supported. Please select this asset home chain to perform operations. +

+ +
+ )} + + {/* Channel-closing overlay */} + {isChannelClosing && ( +
+ +

+ Channel is being closed +

+
+ )} +
+ )} + + {/* Faucet tab content */} + {tab === 'faucet' && ( +
+

+ Drips 10 {selectedAsset.toUpperCase()} every 5 minutes to your account. +

+ +
+ )} +
+ ); +} diff --git a/playground/src/components/ChannelList.tsx b/playground/src/components/ChannelList.tsx new file mode 100644 index 000000000..5f3da73b2 --- /dev/null +++ b/playground/src/components/ChannelList.tsx @@ -0,0 +1,123 @@ +import { useState, useCallback } from 'react'; +import { RefreshCw } from 'lucide-react'; +import type { Client, Channel, Blockchain } from '@yellow-org/sdk'; +import { ChannelStatus } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address } from 'viem'; +import ChannelRow from './ChannelRow'; +import IncomingChannelRow from './IncomingChannelRow'; + +interface Props { + channels: Channel[]; + client: Client | null; + address: Address | null; + chains: Blockchain[]; + currentChainId: bigint | null; + balances: Record; + isLoading: boolean; + closingAsset: string | null; + onRefresh: () => void; + onClose: (asset: string, blockchainId: bigint) => void; + onSwitchToHomeChain: (chainId: bigint) => void; + onSelectAsset: (asset: string) => void; + onAfterOp: () => void; + channelStatesKey?: number; +} + +export default function ChannelList({ + channels, + client, + address, + chains, + currentChainId, + balances, + isLoading, + closingAsset, + onRefresh, + onClose, + onSwitchToHomeChain, + onSelectAsset, + onAfterOp, + channelStatesKey, +}: Props) { + const [expandedIncoming, setExpandedIncoming] = useState>(new Set()); + + const handleIncomingExpandChange = useCallback((asset: string, expanded: boolean) => { + setExpandedIncoming(prev => { + const next = new Set(prev); + if (expanded) next.add(asset.toLowerCase()); + else next.delete(asset.toLowerCase()); + return next; + }); + }, []); + + const channelAssets = new Set( + channels.filter(c => c.status !== ChannelStatus.Closed).map(c => c.asset.toLowerCase()), + ); + const incomingAssets = Object.entries(balances).filter( + ([asset, bal]) => bal.gt(0) && !channelAssets.has(asset.toLowerCase()), + ); + + const isEmpty = channels.length === 0 && incomingAssets.length === 0; + + return ( +
+
+
+ Channels + ({channels.length + incomingAssets.length}) +
+ +
+ +
+ {isEmpty ? ( +

+ {isLoading ? 'Loading…' : 'No channels yet. Deposit to open one.'} +

+ ) : ( + <> + {incomingAssets.map(([asset, bal]) => ( + + ))} + {channels.map(c => ( + + ))} + + )} +
+
+ ); +} diff --git a/playground/src/components/ChannelRow.tsx b/playground/src/components/ChannelRow.tsx new file mode 100644 index 000000000..ec5e5492b --- /dev/null +++ b/playground/src/components/ChannelRow.tsx @@ -0,0 +1,157 @@ +import { useState } from 'react'; +import { ChevronDown, Search } from 'lucide-react'; +import type { Client, Channel, Blockchain } from '@yellow-org/sdk'; +import { ChannelStatus } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address } from 'viem'; +import CopyButton from './CopyButton'; +import StateViewer from './StateViewer'; +import { formatAddress } from '../utils'; +import { chainDisplayName } from '../chainMeta'; + +interface Props { + channel: Channel; + client: Client | null; + address: Address | null; + chains: Blockchain[]; + currentChainId: bigint | null; + enforcedBalance: Decimal | null | undefined; + onClose: (asset: string, blockchainId: bigint) => void; + onSwitchToHomeChain: (chainId: bigint) => void; + onSelectAsset: (asset: string) => void; + onAfterOp?: () => void; + isClosing: boolean; + channelStatesKey?: number; + defaultExpanded?: boolean; +} + +export default function ChannelRow({ + channel, + client, + address, + chains, + currentChainId, + enforcedBalance, + onClose, + onSwitchToHomeChain, + onSelectAsset, + onAfterOp, + isClosing, + channelStatesKey, + defaultExpanded, +}: Props) { + const [expanded, setExpanded] = useState(defaultExpanded ?? false); + const closed = channel.status === ChannelStatus.Closed; + + const homeChainObj = chains.find(c => c.id === channel.blockchainId); + const homeChainName = chainDisplayName(channel.blockchainId, homeChainObj?.name); + const wrongChain = !closed && currentChainId != null && currentChainId !== channel.blockchainId; + + return ( +
+ {/* ── Row header ── */} +
setExpanded(e => !e)} + > + {/* Asset icon */} + + {channel.asset.slice(0, 4).toUpperCase()} + + + {/* Name + ID */} +
+
+ {channel.asset.toUpperCase()} + + + Select asset + + {/* Chain pill */} + + {homeChainName} + + {wrongChain && ( + + wrong chain + + )} + {closed && ( + + closed + + )} +
+
+ {formatAddress(channel.channelId)} + +
+
+ + +
+ + {/* ── Expanded panel ── */} + {expanded && ( +
+ {wrongChain && ( +
+ + Wallet is on a different chain. Switch to "{homeChainName}" to interact. + + +
+ )} + + {!closed && ( +
+ +
+ )} + + {closed ? ( +

Channel closed. Final balance settled on-chain.

+ ) : ( + + )} +
+ )} +
+ ); +} diff --git a/playground/src/components/CopyButton.tsx b/playground/src/components/CopyButton.tsx new file mode 100644 index 000000000..6c62b179f --- /dev/null +++ b/playground/src/components/CopyButton.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react'; +import { Copy, Check } from 'lucide-react'; + +interface Props { + value: string; + className?: string; + size?: number; +} + +export default function CopyButton({ value, className = '', size = 13 }: Props) { + const [copied, setCopied] = useState(false); + + const onClick = async () => { + try { + await navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { + /* clipboard denied */ + } + }; + + return ( + + ); +} diff --git a/playground/src/components/HistoryTab.tsx b/playground/src/components/HistoryTab.tsx new file mode 100644 index 000000000..58b86d7e1 --- /dev/null +++ b/playground/src/components/HistoryTab.tsx @@ -0,0 +1,982 @@ +import { useState, useEffect, useCallback } from 'react'; +import { RefreshCw, ChevronRight, ChevronDown, Filter, X, Copy, Check } from 'lucide-react'; +import { TransactionType } from '@yellow-org/sdk'; +import type { Client, Transaction, Blockchain, State } from '@yellow-org/sdk'; +import type { Address } from 'viem'; +import { tokenIconUrl } from '../icons'; +import { formatAddress, timeAgo } from '../utils'; + +function formatStateId(id: string): string { + if (id.length <= 14) return id; + return `${id.slice(0, 6)}…${id.slice(-6)}`; +} + +const PAGE_SIZE = 25; +const FETCH_LIMIT = 200; + +type TxVariant = 'deposit' | 'withdraw' | 'transfer' | 'finalize' | 'muted'; + +interface HistoryFilters { + types: TransactionType[]; + assets: string[]; + fromAddress: string; + toAddress: string; +} + +interface Props { + client: Client | null; + address: Address | null; + chains: Blockchain[]; +} + +const EMPTY_FILTERS: HistoryFilters = { types: [], assets: [], fromAddress: '', toAddress: '' }; + +const TX_LABELS: Partial> = { + [TransactionType.HomeDeposit]: 'Home Deposit', + [TransactionType.HomeWithdrawal]: 'Home Withdrawal', + [TransactionType.EscrowDeposit]: 'Escrow Deposit', + [TransactionType.EscrowWithdraw]: 'Escrow Withdraw', + [TransactionType.Transfer]: 'Transfer', + [TransactionType.Commit]: 'Commit', + [TransactionType.Release]: 'Release', + [TransactionType.Rebalance]: 'Rebalance', + [TransactionType.Migrate]: 'Migrate', + [TransactionType.EscrowLock]: 'Escrow Lock', + [TransactionType.MutualLock]: 'Mutual Lock', + [TransactionType.Finalize]: 'Finalize', +}; + +const ALL_TX_TYPES = [ + TransactionType.HomeDeposit, + TransactionType.HomeWithdrawal, + TransactionType.EscrowDeposit, + TransactionType.EscrowWithdraw, + TransactionType.Transfer, + TransactionType.Commit, + TransactionType.Release, + TransactionType.Rebalance, + TransactionType.Migrate, + TransactionType.EscrowLock, + TransactionType.MutualLock, + TransactionType.Finalize, +]; + +function txVariant(type: TransactionType): TxVariant { + if (type === TransactionType.HomeDeposit || type === TransactionType.EscrowDeposit) return 'deposit'; + if (type === TransactionType.HomeWithdrawal || type === TransactionType.EscrowWithdraw) return 'withdraw'; + if (type === TransactionType.Transfer) return 'transfer'; + if (type === TransactionType.Finalize) return 'finalize'; + return 'muted'; +} + +const VARIANT_STYLE: Record = { + deposit: { color: 'var(--success)', bg: 'rgba(34,197,94,0.12)' }, + withdraw: { color: 'var(--error)', bg: 'rgba(239,68,68,0.12)' }, + transfer: { color: '#60a5fa', bg: 'rgba(96,165,250,0.12)' }, + finalize: { color: '#a78bfa', bg: 'rgba(167,139,250,0.12)' }, + muted: { color: 'var(--text-muted)', bg: 'rgba(255,255,255,0.06)' }, +}; + +function amountPrefix(type: TransactionType, toAccount?: string, address?: Address | null): string { + const v = txVariant(type); + if (v === 'deposit') return '+'; + if (v === 'withdraw') return '−'; + if (v === 'transfer') { + if (address && toAccount && toAccount.toLowerCase() === address.toLowerCase()) return '+'; + return '−'; + } + return ''; +} + +function isOnChainTx(type: TransactionType): boolean { + return type === TransactionType.HomeDeposit || + type === TransactionType.HomeWithdrawal || + type === TransactionType.EscrowDeposit || + type === TransactionType.EscrowWithdraw || + type === TransactionType.Finalize; +} + +type ConfirmStatus = 'cosigned' | 'pending'; + +function getConfirmStatus( + tx: Transaction, + address: Address | null, + latestStates: Record, +): ConfirmStatus { + // On-chain txs have their own confirmation model; off-chain senders are co-signed synchronously. + if (isOnChainTx(tx.txType)) return 'cosigned'; + if (!address || tx.toAccount.toLowerCase() !== address.toLowerCase()) return 'cosigned'; + + const state = latestStates[tx.asset]; + if (!state || !tx.receiverNewStateId) return 'cosigned'; + + // Pending = latest state IS this receiver state AND the user hasn't signed it yet. + if ( + state.id.toLowerCase() === tx.receiverNewStateId.toLowerCase() && + state.nodeSig && + !state.userSig + ) return 'pending'; + + return 'cosigned'; +} + +// ── Sub-components ──────────────────────────────────────────────────────────── + +function TypeBadge({ type, isActive, onClick }: { + type: TransactionType; + isActive: boolean; + onClick?: (e: React.MouseEvent) => void; +}) { + const label = TX_LABELS[type] ?? `Type ${type}`; + const { color, bg } = VARIANT_STYLE[txVariant(type)]; + return ( + + + {label} + + + ); +} + +function AddrCell({ value, isActive, onClick }: { + value: string; + isActive: boolean; + onClick: (e: React.MouseEvent) => void; +}) { + const [copied, setCopied] = useState(false); + const display = formatAddress(value); + + const onCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + await navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { /* clipboard denied */ } + }; + + return ( +
+ + {display} + + +
+ ); +} + +function AssetCell({ asset, isActive, onClick }: { + asset: string; + isActive: boolean; + onClick: (e: React.MouseEvent) => void; +}) { + const iconUrl = tokenIconUrl(asset); + const [imgErr, setImgErr] = useState(false); + return ( + + {iconUrl && !imgErr ? ( + {asset} setImgErr(true)} + /> + ) : ( + + {asset.slice(0, 1).toUpperCase()} + + )} + {asset.toUpperCase()} + + ); +} + +// ── Filter popover ──────────────────────────────────────────────────────────── + +function FilterIcon({ active }: { active: boolean }) { + return ( + + ); +} + +// ── Detail panel ───────────────────────────────────────────────────────────── + +type TsFormat = 'date' | 'unix'; + +function DetailPanel({ tx, tsFormat, onToggleTsFormat, confirmStatus }: { + tx: Transaction; + tsFormat: TsFormat; + onToggleTsFormat: () => void; + confirmStatus: ConfirmStatus; +}) { + const onChain = isOnChainTx(tx.txType); + const tsDate = tx.createdAt.toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; + const tsUnix = Math.floor(tx.createdAt.getTime() / 1000).toString(); + const tsValue = tsFormat === 'date' ? tsDate : tsUnix; + const tsTip = tsFormat === 'date' ? 'Display as Unix seconds' : 'Display as UTC date'; + + const copyInline = (value: string) => { + navigator.clipboard.writeText(value).catch(() => {}); + }; + + return ( +
+ + + {/* Timestamp — rendered inline so the value itself is the interactive target */} +
+ + Timestamp + + { e.stopPropagation(); onToggleTsFormat(); }} + > + + {tsValue} + + +
+ + {/* Confirmation timeline — spans all 3 columns */} +
+ + Confirmation + + +
+
+ ); +} + +function DetailField({ label, value, displayValue, mono = true, onCopy }: { + label: string; + value?: string; + displayValue?: string; + mono?: boolean; + onCopy?: (v: string) => void; +}) { + const shown = displayValue ?? value; + return ( +
+ {label} + {shown ? ( + + + {shown} + + {onCopy && value && ( + + )} + + ) : ( + + )} +
+ ); +} + +function ConfirmTimeline({ onChain, status }: { onChain: boolean; status: ConfirmStatus }) { + type Step = { label: string; done: boolean }; + const steps: Step[] = onChain + ? [ + { label: 'Signed', done: true }, + { label: 'Broadcasted', done: true }, + { label: 'Confirmed', done: true }, + ] + : [ + { label: 'Signed', done: true }, + { label: 'Co-signed', done: status === 'cosigned' }, + ]; + + return ( +
+ {steps.map(({ label, done }, i) => ( + + + {label}{!done ? '…' : ''} + + {i < steps.length - 1 && ( + + )} + + ))} +
+ ); +} + +// ── Table header with filter popover ───────────────────────────────────────── + +function TxIdCell({ id }: { id: string }) { + const [copied, setCopied] = useState(false); + const display = formatAddress(id); + + const onCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + await navigator.clipboard.writeText(id); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { /* clipboard denied */ } + }; + + return ( +
+ {display} + +
+ ); +} + +function ThFilter({ label, isActive, isOpen, onToggle, width, children }: { + label: string; + isActive: boolean; + isOpen: boolean; + onToggle: (e: React.MouseEvent) => void; + width?: string; + children?: React.ReactNode; +}) { + return ( + +
+
+ {label} + +
+ {isOpen && ( +
e.stopPropagation()}> + {children} +
+ )} +
+ + ); +} + +// ── Main component ──────────────────────────────────────────────────────────── + +export default function HistoryTab({ client, address }: Props) { + const [allTxs, setAllTxs] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const [filters, setFilters] = useState(EMPTY_FILTERS); + // pending filter edits inside popovers before Apply + const [pendingTypes, setPendingTypes] = useState([]); + const [pendingAssets, setPendingAssets] = useState([]); + const [pendingFrom, setPendingFrom] = useState(''); + const [pendingTo, setPendingTo] = useState(''); + + const [latestStates, setLatestStates] = useState>({}); + + const [openPopover, setOpenPopover] = useState<'type' | 'asset' | 'from' | 'to' | null>(null); + const [expandedId, setExpandedId] = useState(null); + const [page, setPage] = useState(1); + const [tsFormat, setTsFormat] = useState('date'); + const [, setTick] = useState(0); + + useEffect(() => { + const id = setInterval(() => setTick(t => t + 1), 30000); + return () => clearInterval(id); + }, []); + + const fetchTxs = useCallback(async () => { + if (!client || !address) return; + setIsLoading(true); + setError(null); + try { + const { transactions } = await client.getTransactions(address, { + pageSize: FETCH_LIMIT, + page: 1, + }); + setAllTxs(transactions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())); + } catch (e) { + setError(e instanceof Error ? e.message : 'Failed to load transactions'); + } finally { + setIsLoading(false); + } + }, [client, address]); + + useEffect(() => { + setPage(1); + fetchTxs(); + }, [fetchTxs]); + + // After transactions load, fetch the latest state for each asset where the user is receiver. + useEffect(() => { + if (!client || !address || allTxs.length === 0) return; + const assets = [...new Set( + allTxs + .filter(tx => !isOnChainTx(tx.txType) && tx.toAccount.toLowerCase() === address.toLowerCase()) + .map(tx => tx.asset) + )]; + if (assets.length === 0) return; + Promise.all( + assets.map(asset => + client.getLatestState(address, asset, false) + .then(state => [asset, state] as const) + .catch(() => [asset, null] as const) + ) + ).then(entries => setLatestStates(Object.fromEntries(entries))); + }, [client, address, allTxs]); + + // Close popover on outside click + useEffect(() => { + if (!openPopover) return; + const handler = (e: MouseEvent) => { + if (!(e.target as Element).closest('.th-popover-wrap')) { + setOpenPopover(null); + } + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [openPopover]); + + // Client-side filter + const filteredTxs = allTxs.filter(tx => { + if (filters.types.length && !filters.types.includes(tx.txType)) return false; + if (filters.assets.length && !filters.assets.includes(tx.asset)) return false; + if (filters.fromAddress && !tx.fromAccount.toLowerCase().includes(filters.fromAddress.toLowerCase())) return false; + if (filters.toAddress && !tx.toAccount.toLowerCase().includes(filters.toAddress.toLowerCase())) return false; + return true; + }); + + const totalCount = filteredTxs.length; + const pageCount = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); + const pageTxs = filteredTxs.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); + + const availableAssets = [...new Set(allTxs.map(t => t.asset))].sort(); + + // Active filter count for the badge + const activeFilterCount = + (filters.types.length ? 1 : 0) + + (filters.assets.length ? 1 : 0) + + (filters.fromAddress ? 1 : 0) + + (filters.toAddress ? 1 : 0); + + // ── Quick filter handlers ────────────────────────────────────────────────── + + const toggleType = (type: TransactionType, e: React.MouseEvent) => { + e.stopPropagation(); + setFilters(f => ({ + ...f, + types: f.types.includes(type) ? f.types.filter(t => t !== type) : [...f.types, type], + })); + setPage(1); + }; + + const toggleAsset = (asset: string, e: React.MouseEvent) => { + e.stopPropagation(); + setFilters(f => ({ + ...f, + assets: f.assets.includes(asset) ? f.assets.filter(a => a !== asset) : [...f.assets, asset], + })); + setPage(1); + }; + + const toggleFrom = (addr: string, e: React.MouseEvent) => { + e.stopPropagation(); + setFilters(f => ({ ...f, fromAddress: f.fromAddress === addr ? '' : addr })); + setPage(1); + }; + + const toggleTo = (addr: string, e: React.MouseEvent) => { + e.stopPropagation(); + setFilters(f => ({ ...f, toAddress: f.toAddress === addr ? '' : addr })); + setPage(1); + }; + + // ── Popover open/close helpers ───────────────────────────────────────────── + + const openFilter = (col: typeof openPopover, e: React.MouseEvent) => { + e.stopPropagation(); + if (openPopover === col) { + setOpenPopover(null); + return; + } + // Sync pending state with current filters + setPendingTypes(filters.types); + setPendingAssets(filters.assets); + setPendingFrom(filters.fromAddress); + setPendingTo(filters.toAddress); + setOpenPopover(col); + }; + + const applyTypeFilter = () => { + setFilters(f => ({ ...f, types: pendingTypes })); + setOpenPopover(null); + setPage(1); + }; + + const applyAssetFilter = () => { + setFilters(f => ({ ...f, assets: pendingAssets })); + setOpenPopover(null); + setPage(1); + }; + + const applyFromFilter = () => { + setFilters(f => ({ ...f, fromAddress: pendingFrom })); + setOpenPopover(null); + setPage(1); + }; + + const applyToFilter = () => { + setFilters(f => ({ ...f, toAddress: pendingTo })); + setOpenPopover(null); + setPage(1); + }; + + const clearFilter = (col: keyof HistoryFilters) => { + setFilters(f => ({ + ...f, + ...(col === 'types' ? { types: [] } : {}), + ...(col === 'assets' ? { assets: [] } : {}), + ...(col === 'fromAddress' ? { fromAddress: '' } : {}), + ...(col === 'toAddress' ? { toAddress: '' } : {}), + })); + setOpenPopover(null); + setPage(1); + }; + + // ── Render ───────────────────────────────────────────────────────────────── + + const thBase: React.CSSProperties = { + padding: '10px 12px', + textAlign: 'left', + fontWeight: 500, + fontSize: '12px', + color: 'var(--text-muted)', + whiteSpace: 'nowrap', + borderBottom: '1px solid var(--border)', + background: 'var(--bg-elevated)', + }; + + const tdBase: React.CSSProperties = { + padding: '10px 12px', + verticalAlign: 'middle', + borderBottom: '1px solid var(--border)', + }; + + return ( +
+ {/* Card header */} +
+
+ Transaction History + {activeFilterCount > 0 && ( + + {activeFilterCount} filter{activeFilterCount !== 1 ? 's' : ''} + + )} +
+
+ {activeFilterCount > 0 && ( + + )} + +
+
+ + {/* Error state */} + {error && ( +
+ {error} +
+ )} + + {/* Empty / loading state */} + {!error && !isLoading && allTxs.length === 0 && ( +
+ {client && address ? 'No transactions found.' : 'Connect wallet to view history.'} +
+ )} + + {/* Table */} + {(allTxs.length > 0 || isLoading) && ( +
+ + + + {/* expand chevron */} + + + {/* Type with filter popover */} + 0} + isOpen={openPopover === 'type'} + onToggle={e => openFilter('type', e)} + > +
Filter by type
+
+ {ALL_TX_TYPES.map(t => ( + + ))} +
+
+ + +
+
+ + {/* Asset with filter popover */} + 0} + isOpen={openPopover === 'asset'} + onToggle={e => openFilter('asset', e)} + width="15%" + > +
Filter by asset
+
+ {availableAssets.map(a => ( + + ))} +
+
+ + +
+
+ + {/* Amount */} + + + {/* From with filter popover */} + openFilter('from', e)} + width="15%" + > +
Filter by from address
+ setPendingFrom(e.target.value)} + onKeyDown={e => e.key === 'Enter' && applyFromFilter()} + autoFocus + /> +
+ + +
+
+ + {/* To with filter popover */} + openFilter('to', e)} + width="15%" + > +
Filter by to address
+ setPendingTo(e.target.value)} + onKeyDown={e => e.key === 'Enter' && applyToFilter()} + autoFocus + /> +
+ + +
+
+ + {/* Tx ID */} + + + + + {isLoading && allTxs.length === 0 ? ( + + + + ) : pageTxs.length === 0 ? ( + + + + ) : ( + pageTxs.map(tx => { + const isExpanded = expandedId === tx.id; + const variant = txVariant(tx.txType); + const { color: amtColor } = VARIANT_STYLE[variant]; + const prefix = amountPrefix(tx.txType, tx.toAccount, address); + const confirmStatus = getConfirmStatus(tx, address, latestStates); + + return ( + <> + setExpandedId(isExpanded ? null : tx.id)} + style={{ + cursor: 'pointer', + background: isExpanded ? 'rgba(255,255,255,0.02)' : undefined, + transition: 'background 0.1s', + }} + onMouseEnter={e => { if (!isExpanded) (e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.02)'; }} + onMouseLeave={e => { if (!isExpanded) (e.currentTarget as HTMLElement).style.background = ''; }} + > + {/* Expand chevron */} + + + {/* Time */} + + + {/* Type */} + + + {/* Asset */} + + + {/* Amount */} + + + {/* From */} + + + {/* To */} + + + {/* Tx ID */} + + + + {/* Expanded detail */} + {isExpanded && ( + + + + )} + + ); + }) + )} + +
+ + {/* Time */} + TimeAmountTx ID
+ + Loading transactions… +
+ No transactions match the current filters. +
+ {isExpanded + ? + : } + + + {timeAgo(tx.createdAt)} + + + toggleType(tx.txType, e)} + /> + + toggleAsset(tx.asset, e)} + /> + + + {prefix}{tx.amount.toString()} + + + toggleFrom(tx.fromAccount, e)} + /> + + toggleTo(tx.toAccount, e)} + /> + + {tx.id ? : } +
+ setTsFormat(f => f === 'date' ? 'unix' : 'date')} + confirmStatus={confirmStatus} + /> +
+
+ )} + + {/* Pagination footer */} + {totalCount > 0 && ( +
+ + Showing {Math.min((page - 1) * PAGE_SIZE + 1, totalCount)}–{Math.min(page * PAGE_SIZE, totalCount)} of {totalCount} transaction{totalCount !== 1 ? 's' : ''} + +
+ + + {page} / {pageCount} + + +
+
+ )} +
+ ); +} diff --git a/playground/src/components/IncomingChannelRow.tsx b/playground/src/components/IncomingChannelRow.tsx new file mode 100644 index 000000000..80b145646 --- /dev/null +++ b/playground/src/components/IncomingChannelRow.tsx @@ -0,0 +1,147 @@ +import { useState, useEffect } from 'react'; +import { ChevronDown, Search } from 'lucide-react'; +import { toast } from 'sonner'; +import type { Client, State } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address } from 'viem'; +import { showErrorToast } from '../toastError'; +import { formatBalance } from '../utils'; + +interface Props { + asset: string; + balance: Decimal; + client: Client | null; + address: Address | null; + currentChainId: bigint | null; + onSelectAsset: (asset: string) => void; + onAfterAck: () => void; + onExpandChange?: (asset: string, expanded: boolean) => void; +} + +export default function IncomingChannelRow({ asset, balance, client, address, currentChainId, onSelectAsset, onAfterAck, onExpandChange }: Props) { + const [expanded, setExpanded] = useState(false); + + const toggleExpanded = () => { + const next = !expanded; + setExpanded(next); + onExpandChange?.(asset, next); + }; + const [issuedState, setIssuedState] = useState(null); + const [isAcknowledging, setIsAcknowledging] = useState(false); + + useEffect(() => { + if (!client || !address) return; + client.getLatestState(address, asset, false) + .then(s => setIssuedState(s)) + .catch(() => {}); + }, [client, address, asset]); + + const handleAcknowledge = async (e: React.MouseEvent) => { + e.stopPropagation(); + if (!client || !currentChainId) return; + setIsAcknowledging(true); + try { + await client.setHomeBlockchain(asset, currentChainId); + await client.acknowledge(asset); + toast.success(`Acknowledged ${asset.toUpperCase()} receipt`); + onAfterAck(); + } catch (err) { + const error = err as { code?: number; message?: string }; + if (error?.code === 4001) { + toast('Cancelled'); + } else { + showErrorToast(`Acknowledge failed: ${error?.message ?? String(err)}`); + } + } finally { + setIsAcknowledging(false); + } + }; + + const displayAmount = (issuedState as { homeLedger?: { userBalance?: Decimal } } | null)?.homeLedger?.userBalance ?? balance; + + return ( +
+
+ + {asset.slice(0, 4).toUpperCase()} + + +
+
+ {asset.toUpperCase()} + + + Select asset + + + + NO HOME CHAIN + + Chain the acknowledge is invoked on will become the Home chain + +
+
+ {formatBalance(displayAmount)} incoming +
+
+ + +
+ + {expanded && ( +
+
+
+ State + Version + Amount + +
+
+
+ + Issued + + ? + Node proposed this state. Acknowledge to co-sign and open the channel. + +
+ + {issuedState ? `v${issuedState.version.toString()}` : '—'} + + + {formatBalance(displayAmount)} {asset.toUpperCase()} + + + + +
+
+
+ )} +
+ ); +} diff --git a/playground/src/components/PendingReceipts.tsx b/playground/src/components/PendingReceipts.tsx new file mode 100644 index 000000000..8d873c3ae --- /dev/null +++ b/playground/src/components/PendingReceipts.tsx @@ -0,0 +1,92 @@ +import { useState } from 'react'; +import { toast } from 'sonner'; +import { showErrorToast } from "../toastError"; +import type { Client, Channel } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import { Inbox } from 'lucide-react'; +import { formatBalance } from '../utils'; + +interface Props { + client: Client | null; + channels: Channel[]; + balances: Record; + onAfterAck: () => void; +} + +export default function PendingReceipts({ client, channels, balances, onAfterAck }: Props) { + const channelAssets = new Set(channels.map(c => c.asset)); + const pending = Object.entries(balances).filter( + ([asset, bal]) => bal.gt(0) && !channelAssets.has(asset), + ); + + const [acking, setAcking] = useState(null); + + if (pending.length === 0) return null; + + const acknowledge = async (asset: string) => { + if (!client) return; + setAcking(asset); + try { + await client.acknowledge(asset); + toast.success(`Acknowledged ${asset.toUpperCase()} receipt`); + onAfterAck(); + } catch (err) { + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) { + toast('Cancelled'); + } else { + showErrorToast(`Acknowledge failed: ${e?.message ?? String(err)}`); + } + } finally { + setAcking(null); + } + }; + + return ( +
+
+
+ + Pending receipts + ({pending.length}) +
+
+
+

+ You received these assets but haven't co-signed the state yet. Acknowledge to claim the balance and open + a channel. +

+ {pending.map(([asset, bal]) => ( +
+
+ + {asset.slice(0, 4).toUpperCase()} + +
+
{asset.toUpperCase()}
+
{formatBalance(bal)} incoming
+
+
+ +
+ ))} +
+
+ ); +} diff --git a/playground/src/components/SessionKeyBanner.tsx b/playground/src/components/SessionKeyBanner.tsx new file mode 100644 index 000000000..ed10f5ef9 --- /dev/null +++ b/playground/src/components/SessionKeyBanner.tsx @@ -0,0 +1,32 @@ +import { Key } from 'lucide-react'; + +interface Props { + onSetup: () => void; +} + +export default function SessionKeyBanner({ onSetup }: Props) { + return ( +
+
+
+ +
+
+
Skip the MetaMask popups
+
+ Authorize a 24h session key to sign state updates without prompts. +
+
+
+ +
+ ); +} diff --git a/playground/src/components/SessionKeyRegisterForm.tsx b/playground/src/components/SessionKeyRegisterForm.tsx new file mode 100644 index 000000000..915fa6400 --- /dev/null +++ b/playground/src/components/SessionKeyRegisterForm.tsx @@ -0,0 +1,371 @@ +import { useState, useEffect, useMemo } from 'react'; +import { Key } from 'lucide-react'; + +interface SessionKeyRegisterFormProps { + mode: 'register' | 'update'; + initialAssets?: string[]; + initialExpiryUnix?: bigint; + supportedAssets: string[]; + isSubmitting: boolean; + onSubmit: (assets: string[], expiresAt: bigint) => Promise; + onCancel: () => void; +} + +type ExpiryMode = 'duration' | 'date' | 'unix'; + +export default function SessionKeyRegisterForm({ + mode, + initialAssets, + initialExpiryUnix, + supportedAssets, + isSubmitting, + onSubmit, + onCancel, +}: SessionKeyRegisterFormProps) { + const [selectedAssets, setSelectedAssets] = useState( + initialAssets ?? [...supportedAssets] + ); + + const [expiryMode, setExpiryMode] = useState('duration'); + + const [durationValue, setDurationValue] = useState('24'); + const [durationUnit, setDurationUnit] = useState<'minutes' | 'hours' | 'days'>('hours'); + + const [dateValue, setDateValue] = useState(''); + + const [unixValue, setUnixValue] = useState(''); + + useEffect(() => { + if (initialExpiryUnix) { + const expDate = new Date(Number(initialExpiryUnix) * 1000); + setDateValue(expDate.toISOString().slice(0, 16)); + setUnixValue(initialExpiryUnix.toString()); + const secsFromNow = Number(initialExpiryUnix) - Math.floor(Date.now() / 1000); + if (secsFromNow > 0) { + const hoursFromNow = Math.ceil(secsFromNow / 3600); + setDurationValue(String(hoursFromNow)); + } + } + }, [initialExpiryUnix]); + + const computedExpiresAt: bigint | null = useMemo(() => { + const now = Math.floor(Date.now() / 1000); + try { + if (expiryMode === 'duration') { + const val = parseInt(durationValue, 10); + if (!val || val <= 0) return null; + const seconds = durationUnit === 'minutes' ? val * 60 : durationUnit === 'hours' ? val * 3600 : val * 86400; + return BigInt(now + seconds); + } + if (expiryMode === 'date') { + if (!dateValue) return null; + const ts = Math.floor(new Date(dateValue).getTime() / 1000); + if (ts <= now + 60) return null; + return BigInt(ts); + } + if (expiryMode === 'unix') { + const ts = parseInt(unixValue, 10); + if (!ts || ts <= now + 60) return null; + return BigInt(ts); + } + } catch { + return null; + } + return null; + }, [expiryMode, durationValue, durationUnit, dateValue, unixValue]); + + const computedExpiryDisplay = computedExpiresAt + ? new Date(Number(computedExpiresAt) * 1000).toISOString().replace('T', ' ').slice(0, 19) + ' UTC' + : '—'; + + const isValid = selectedAssets.length > 0 && computedExpiresAt !== null; + + const handleSubmit = async () => { + if (!isValid || !computedExpiresAt) return; + await onSubmit(selectedAssets, computedExpiresAt); + }; + + const toggleAsset = (asset: string) => { + setSelectedAssets(prev => + prev.includes(asset) ? prev.filter(a => a !== asset) : [...prev, asset] + ); + }; + + return ( +
+
+ {/* Header */} +
+
+ +
+

+ {mode === 'update' ? 'Update session key' : 'Register a new session key'} +

+

+ {mode === 'update' + ? 'Modify the assets or extend the expiry. A new version will be signed.' + : 'A session key signs state updates on your behalf — no MetaMask for every operation.'} +

+
+ + {/* Divider */} +
+ + {/* Assets + Expiry side by side */} +
+ + {/* Assets column */} +
+
+ + Assets + + + + · + + +
+ {/* Scrollable asset list — shows ~3 items before scrolling */} +
+ {supportedAssets.map(asset => { + const checked = selectedAssets.includes(asset); + return ( +
toggleAsset(asset)} + style={{ + display: 'flex', + alignItems: 'center', + gap: 10, + padding: '8px 12px', + borderRadius: 8, + cursor: 'pointer', + background: checked ? 'var(--accent-dim)' : 'var(--bg-elevated)', + border: `1px solid ${checked ? 'var(--accent)' : 'var(--border)'}`, + transition: 'border-color 0.15s, background 0.15s', + flexShrink: 0, + }} + > +
+ {checked && ( + + + + )} +
+ {asset} +
+ ); + })} +
+ {selectedAssets.length === 0 && ( +

Select at least one.

+ )} +
+ + {/* Vertical separator */} +
+ + {/* Expiry column */} +
+
+ Expiration +
+ + {/* Segmented control */} +
+ {(['duration', 'date', 'unix'] as ExpiryMode[]).map(m => ( + + ))} +
+ + {expiryMode === 'duration' && ( +
+ setDurationValue(e.target.value)} + style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', padding: '7px 10px', fontFamily: 'JetBrains Mono, monospace', fontSize: 13, color: 'var(--text-primary)', minWidth: 0 }} + placeholder="24" + /> + +
+ )} + + {expiryMode === 'date' && ( +
+ setDateValue(e.target.value)} + style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', padding: '7px 10px', fontSize: 12, color: 'var(--text-primary)', width: '100%', fontFamily: 'inherit' }} + /> +
+ )} + + {expiryMode === 'unix' && ( +
+ setUnixValue(e.target.value)} + placeholder="1748424600" + style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', padding: '7px 10px', fontFamily: 'JetBrains Mono, monospace', fontSize: 13, color: 'var(--text-primary)', width: '100%' }} + /> +
+ )} + +
+ Expires:{' '} + + {computedExpiryDisplay} + +
+
+
+ + {/* Divider */} +
+ + {/* Summary box */} +
+ This key will authorize:{' '} + + {selectedAssets.length > 0 ? selectedAssets.join(', ') : 'no assets selected'} + {' '} + and expire in{' '} + + {computedExpiresAt + ? expiryMode === 'duration' + ? `${durationValue} ${durationUnit === 'minutes' ? 'min' : durationUnit}` + : computedExpiryDisplay + : '—'} + + .
+ On-chain operations (deposit, checkpoint, approve) will still require MetaMask. + {mode === 'update' && ( + <> +
+ Version will be incremented from the current one. + + )} +
+ + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/playground/src/components/SessionKeyRevokeModal.tsx b/playground/src/components/SessionKeyRevokeModal.tsx new file mode 100644 index 000000000..eb36de920 --- /dev/null +++ b/playground/src/components/SessionKeyRevokeModal.tsx @@ -0,0 +1,93 @@ +import type { Address } from 'viem'; +import { AlertTriangle } from 'lucide-react'; + +interface SessionKeyRevokeModalProps { + sessionKeyAddress: Address; + isSubmitting: boolean; + onConfirm: () => Promise; + onCancel: () => void; +} + +export default function SessionKeyRevokeModal({ + sessionKeyAddress, + isSubmitting, + onConfirm, + onCancel, +}: SessionKeyRevokeModalProps) { + return ( +
+
+ {/* Header */} +
+
+ +
+

Revoke session key

+

+ This key will no longer authorize any operations. This action requires a MetaMask + signature. +

+
+ + {/* Key address box */} +
+ Key: + + {sessionKeyAddress.slice(0, 10)}…{sessionKeyAddress.slice(-8)} + +
+ + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/playground/src/components/SessionKeySetupModal.tsx b/playground/src/components/SessionKeySetupModal.tsx new file mode 100644 index 000000000..dbf387e3b --- /dev/null +++ b/playground/src/components/SessionKeySetupModal.tsx @@ -0,0 +1,67 @@ +import { Key } from 'lucide-react'; + +interface Props { + assets: string[]; + isRegistering: boolean; + mode: 'setup' | 'renew'; + onConfirm: () => void; + onCancel: () => void; +} + +export default function SessionKeySetupModal({ assets, isRegistering, mode, onConfirm, onCancel }: Props) { + return ( +
+
+
+ +
+

+ {mode === 'renew' ? 'Renew session key' : 'Set up a session key'} +

+

+ A session key is a temporary key that signs state updates on your behalf, so you stop seeing a MetaMask + popup for every deposit, withdraw, or transfer. +

+ +
    +
  • + Expires in 24 hours. You'll be asked to renew. +
  • +
  • + Authorizes all currently supported assets + {assets.length > 0 && ( + ({assets.join(', ').toUpperCase()}) + )} + . +
  • +
  • + On-chain transactions still require MetaMask (deposit funds, + checkpoint, approve token). +
  • +
  • Stored in this browser's localStorage. Clear anytime from the wallet bar.
  • +
+ +

+ Confirm to sign one MetaMask request and authorize the session key. +

+ +
+ + +
+
+
+ ); +} diff --git a/playground/src/components/SessionKeysTab.tsx b/playground/src/components/SessionKeysTab.tsx new file mode 100644 index 000000000..d1b696f86 --- /dev/null +++ b/playground/src/components/SessionKeysTab.tsx @@ -0,0 +1,413 @@ +import { useState, useEffect } from 'react'; +import { Key, RefreshCw, Plus, Copy, Check, Eye, EyeOff } from 'lucide-react'; +import type { Address, WalletClient } from 'viem'; +import type { Client, ChannelSessionKeyStateV1 } from '@yellow-org/sdk'; +import type { StoredSessionKey } from '../sessionKey'; +import { useSessionKeyManagement } from '../hooks/useSessionKeyManagement'; +import SessionKeyRegisterForm from './SessionKeyRegisterForm'; +import SessionKeyRevokeModal from './SessionKeyRevokeModal'; + +// ── Expiry format cycling ───────────────────────────────────────────────────── + +type ExpFormat = 'relative' | 'date' | 'unix'; + +const NEXT_FMT: Record = { relative: 'date', date: 'unix', unix: 'relative' }; +const FMT_HINT: Record = { + relative: 'Show as UTC date', + date: 'Show as Unix timestamp', + unix: 'Show as relative time', +}; + +function formatExpiry(expiresAtStr: string, fmt: ExpFormat): string { + const expiresAt = Number(expiresAtStr); + const now = Math.floor(Date.now() / 1000); + if (fmt === 'unix') return expiresAtStr; + if (fmt === 'date') return new Date(expiresAt * 1000).toISOString().replace('T', ' ').slice(0, 19) + ' UTC'; + const diff = expiresAt - now; + if (diff <= 0) return 'expired'; + const h = Math.floor(diff / 3600); + const m = Math.floor((diff % 3600) / 60); + return h > 0 ? `${h}h ${m}m` : `${m}m`; +} + +// ── Status derivation ───────────────────────────────────────────────────────── + +type KeyStatus = 'active' | 'expiring' | 'expired' | 'revoked'; + +function getKeyStatus(key: ChannelSessionKeyStateV1): KeyStatus { + const now = Math.floor(Date.now() / 1000); + const expiresAt = Number(key.expires_at); + // Revoked = registered with expiresAt well in the past (via revoke flow). + // Check before 'expired' so the more specific branch wins. + if (expiresAt < now - 60) return 'revoked'; + if (expiresAt <= now) return 'expired'; + if (expiresAt - now < 3600) return 'expiring'; // < 1 hour + return 'active'; +} + +// ── Status badge ────────────────────────────────────────────────────────────── + +const STATUS_STYLE: Record = { + active: { bg: 'rgba(34,197,94,0.12)', color: '#22c55e', label: 'Active' }, + expiring: { bg: 'rgba(249,115,22,0.14)', color: '#f97316', label: 'Expiring Soon' }, + expired: { bg: 'rgba(102,102,102,0.14)', color: 'var(--text-muted)', label: 'Expired' }, + revoked: { bg: 'rgba(239,68,68,0.12)', color: 'var(--error)', label: 'Revoked' }, +}; + +function StatusBadge({ status }: { status: KeyStatus }) { + const s = STATUS_STYLE[status]; + return ( + + + {s.label} + + ); +} + +// ── Props ───────────────────────────────────────────────────────────────────── + +interface Props { + client: Client | null; + walletClient: WalletClient | null; + address: Address | null; + sessionKey: StoredSessionKey | null; + allSessionKeys: StoredSessionKey[]; + supportedAssets: string[]; + onKeyActivated: (sk: StoredSessionKey) => void; + onKeyCleared: () => void; + onSelectKey: (sessionKeyAddress: Address) => void; + onRefreshAllKeys: () => void; +} + +// ── Main component ──────────────────────────────────────────────────────────── + +export default function SessionKeysTab({ + client, + walletClient, + address, + sessionKey, + allSessionKeys, + supportedAssets, + onKeyActivated, + onKeyCleared, + onSelectKey, + onRefreshAllKeys, +}: Props) { + const [expFmt, setExpFmt] = useState('relative'); + const [showExpired, setShowExpired] = useState(true); + const [showRegisterModal, setShowRegisterModal] = useState(false); + const [keyForUpdate, setKeyForUpdate] = useState(null); + const [keyForRevoke, setKeyForRevoke] = useState(null); + const [copiedAddress, setCopiedAddress] = useState(null); + + const mgmt = useSessionKeyManagement(client, address, walletClient); + + // Fetch on mount + useEffect(() => { mgmt.fetchKeys(); }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Copy address helper + const copyAddr = (addr: string) => { + navigator.clipboard.writeText(addr).catch(() => {}); + setCopiedAddress(addr); + setTimeout(() => setCopiedAddress(null), 1500); + }; + + // Check if any key is expiring soon (for warning banner) + const expiringKey = mgmt.serverKeys.find(k => getKeyStatus(k) === 'expiring'); + + const displayKeys = showExpired + ? mgmt.serverKeys + : mgmt.serverKeys.filter(k => { const s = getKeyStatus(k); return s !== 'expired' && s !== 'revoked'; }); + + return ( +
+ {/* Expiring Soon Banner */} + {expiringKey && ( +
+ {/* key icon circle */} +
+ +
+
+
Session key expiring soon
+
+ {expiringKey.session_key.slice(0, 6)}…{expiringKey.session_key.slice(-4)} + {' '}· {formatExpiry(expiringKey.expires_at, 'relative')} remaining +
+
+ +
+ )} + + {/* Main card */} +
+ {/* Card header */} +
+
+ + Session Keys + + {mgmt.serverKeys.length} + +
+
+ + +
+
+ + {/* Table or empty state */} + {mgmt.isLoading && mgmt.serverKeys.length === 0 ? ( +
+ +
+ ) : mgmt.serverKeys.length === 0 ? ( +
+
+ No session keys yet. Register one to sign state updates without MetaMask prompts. +
+ +
+ ) : ( +
+ + + + + + + + + + + + + {displayKeys.map(key => { + const status = getKeyStatus(key); + const isTerminal = status === 'expired' || status === 'revoked'; + const hasLocalKey = allSessionKeys.some( + lk => lk.sessionKeyAddress.toLowerCase() === key.session_key.toLowerCase(), + ); + const isCurrentKey = sessionKey?.sessionKeyAddress.toLowerCase() === key.session_key.toLowerCase(); + const rowOpacity = status === 'revoked' ? 0.55 : status === 'expired' ? 0.7 : 1; + + return ( + + {/* Address */} + + + {/* Assets */} + + + {/* Expiration */} + + + {/* Status */} + + + {/* Version */} + + + {/* Actions */} + + + ); + })} + +
AddressAssets setExpFmt(f => NEXT_FMT[f])} + title={FMT_HINT[expFmt]} + > + Expiration ↕ + +
+ + Status +
+
VersionActions
+
+ + + {key.session_key.slice(0, 6)}…{key.session_key.slice(-4)} + + {isCurrentKey && ( + + IN USE + + )} + +
+
+ + {key.assets.join(', ')} + + + setExpFmt(f => NEXT_FMT[f])} + data-tip={FMT_HINT[expFmt]} + style={{ color: status === 'expiring' ? '#f97316' : undefined }} + > + {formatExpiry(key.expires_at, expFmt)} + + + + + v{key.version} + +
+ {status === 'expiring' && ( + + )} + {status === 'active' && ( + + )} + {isTerminal && hasLocalKey && ( + + )} + {!isTerminal && ( + + )} + {!isTerminal && !isCurrentKey && ( + + )} +
+
+
+ )} +
+ + {/* Register / Update modal */} + {(showRegisterModal || keyForUpdate) && ( + { + if (!address) return; + if (keyForUpdate) { + const sk = await mgmt.update(address, keyForUpdate, assets, expiresAt); + if (sk) onKeyActivated(sk); + } else { + const sk = await mgmt.register(address, assets, expiresAt); + if (sk) onKeyActivated(sk); + } + setShowRegisterModal(false); + setKeyForUpdate(null); + }} + onCancel={() => { setShowRegisterModal(false); setKeyForUpdate(null); }} + /> + )} + + {/* Revoke confirmation modal */} + {keyForRevoke && ( + { + if (!address) return; + await mgmt.revoke(address, keyForRevoke); + if (sessionKey?.sessionKeyAddress.toLowerCase() === keyForRevoke.session_key.toLowerCase()) { + onKeyCleared(); + } + onRefreshAllKeys(); + setKeyForRevoke(null); + }} + onCancel={() => setKeyForRevoke(null)} + /> + )} +
+ ); +} diff --git a/playground/src/components/SetHomechainModal.tsx b/playground/src/components/SetHomechainModal.tsx new file mode 100644 index 000000000..0a59008bb --- /dev/null +++ b/playground/src/components/SetHomechainModal.tsx @@ -0,0 +1,75 @@ +import { useState } from 'react'; +import type { Asset, Blockchain } from '@yellow-org/sdk'; + +interface Props { + asset: string; + assets: Asset[]; + chains: Blockchain[]; + onConfirm: (asset: string, chainId: bigint) => void; + onCancel: () => void; +} + +export default function SetHomechainModal({ asset, assets, chains, onConfirm, onCancel }: Props) { + const tokens = assets.find(a => a.symbol === asset)?.tokens ?? []; + const supportedChainIds = new Set(tokens.map(t => t.blockchainId.toString())); + const eligible = chains.filter(c => supportedChainIds.has(c.id.toString())); + const [selected, setSelected] = useState(eligible[0]?.id ?? null); + + return ( +
+
+
+

Select home blockchain

+ + {asset.toUpperCase()} + +
+

+ Choose the chain where this asset will settle on-chain. You can only set this once per asset. +

+ +
+ {eligible.length === 0 ? ( +

No chains support this asset.

+ ) : ( + eligible.map(c => ( + + )) + )} +
+ +
+ + +
+
+
+ ); +} diff --git a/playground/src/components/StateViewer.tsx b/playground/src/components/StateViewer.tsx new file mode 100644 index 000000000..50128dcda --- /dev/null +++ b/playground/src/components/StateViewer.tsx @@ -0,0 +1,142 @@ +import type { Client } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address } from 'viem'; +import { useChannelStates } from '../hooks/useChannelStates'; +import { formatBalance } from '../utils'; + +interface Props { + client: Client | null; + address: Address | null; + asset: string; + enforcedBalance: Decimal | null | undefined; + onAfterOp?: () => void; + isLocked?: boolean; + refreshKey?: number; +} + +export default function StateViewer({ client, address, asset, enforcedBalance, onAfterOp, isLocked, refreshKey }: Props) { + const { + enforced, + signed, + issued, + isLoading, + error, + canAcknowledge, + canCheckpoint, + acknowledge, + checkpoint, + isAcknowledging, + isCheckpointing, + } = useChannelStates(client, address, asset, enforcedBalance, onAfterOp, refreshKey); + + if (isLoading && !enforced && !signed && !issued) { + return
Loading states…
; + } + if (error) { + return
Failed to fetch states: {error}
; + } + if (!enforced && !signed && !issued) { + return
No state yet
; + } + + return ( +
+
+ State + Version + Amount + +
+ + + + {isCheckpointing || isLocked ? : 'Checkpoint'} + + ) : null + } + /> + + {isAcknowledging || isLocked ? : 'Acknowledge'} + + ) : null + } + isLast + /> +
+ ); +} + +interface RowProps { + label: string; + dotColor: 'success' | 'accent' | 'blue'; + tooltip: string; + version?: bigint; + amount?: Decimal; + asset: string; + action?: React.ReactNode; + isLast?: boolean; +} + +function StateRow({ label, dotColor, tooltip, version, amount, asset, action, isLast }: RowProps) { + const colorMap = { + success: 'var(--success)', + accent: 'var(--accent)', + blue: '#60a5fa', + }; + return ( +
+
+ + {label} + + ? + {tooltip} + +
+ + {version != null ? `v${version.toString()}` : '—'} + + + {amount != null ? `${formatBalance(amount)} ${asset.toUpperCase()}` : '—'} + + {action ?? } +
+ ); +} diff --git a/playground/src/components/TokenSelector.tsx b/playground/src/components/TokenSelector.tsx new file mode 100644 index 000000000..66a12783a --- /dev/null +++ b/playground/src/components/TokenSelector.tsx @@ -0,0 +1,227 @@ +import { useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { ChevronDown, Droplets } from 'lucide-react'; +import type { Asset, Blockchain, Channel } from '@yellow-org/sdk'; +import { ChannelType, ChannelStatus } from '@yellow-org/sdk'; +import { tokenIconUrl, chainIconUrl } from '../icons'; +import { chainDisplayName } from '../chainMeta'; +import { FAUCET_ASSETS } from '../utils'; + +interface Props { + assets: Asset[]; + selectedAsset: string; + onSelectAsset: (symbol: string) => void; + channels: Channel[]; + chains: Blockchain[]; + disabled?: boolean; +} + +export default function TokenSelector({ + assets, + selectedAsset, + onSelectAsset, + channels, + chains, + disabled, +}: Props) { + const [open, setOpen] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (!open) return; + const onDown = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener('mousedown', onDown); + return () => document.removeEventListener('mousedown', onDown); + }, [open]); + + const selectedObj = assets.find(a => a.symbol === selectedAsset); + + return ( +
+ + + {open && ( +
+ {assets.map(asset => ( + + ))} +
+ )} +
+ ); +} + +function AssetRow({ + asset, + channels, + chains, +}: { + asset: Asset; + channels: Channel[]; + chains: Blockchain[]; +}) { + const homeChannel = channels.find( + c => + c.asset.toLowerCase() === asset.symbol.toLowerCase() && + c.type === ChannelType.Home && + c.status !== ChannelStatus.Closed, + ); + + const assetChains = asset.tokens + .map(t => chains.find(c => c.id === t.blockchainId)) + .filter((c): c is Blockchain => c !== undefined) + .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + + return ( + <> + + + {asset.symbol.toUpperCase()} + {FAUCET_ASSETS.has(asset.symbol.toLowerCase()) && ( + + + Faucet available + + )} + +
+ {assetChains.map(chain => ( + + ))} +
+ + ); +} + +function TokenIcon({ symbol, size }: { symbol: string; size: number }) { + const [errored, setErrored] = useState(false); + const url = tokenIconUrl(symbol); + + if (!url || errored) { + return ( + + {symbol[0]?.toUpperCase()} + + ); + } + + return ( + {symbol} setErrored(true)} + /> + ); +} + +const CHAIN_ICON_SIZE = 20; + +function ChainIcon({ chain, dimmed }: { chain: Blockchain; dimmed: boolean }) { + const [errored, setErrored] = useState(false); + const [tooltip, setTooltip] = useState<{ x: number; y: number } | null>(null); + const url = chainIconUrl(chain.id); + const dimStyle = dimmed ? { opacity: 0.2, filter: 'grayscale(1)' } : undefined; + const displayName = chainDisplayName(chain.id, chain.name); + + const handleMouseEnter = (e: React.MouseEvent) => { + const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); + setTooltip({ x: rect.left + rect.width / 2, y: rect.top }); + }; + + const icon = !url || errored ? ( + + {displayName[0]?.toUpperCase()} + + ) : ( + {displayName} setErrored(true)} + /> + ); + + return ( + setTooltip(null)} + > + {icon} + {tooltip && createPortal( +
+ {displayName} +
, + document.body, + )} +
+ ); +} diff --git a/playground/src/components/UnsupportedChainModal.tsx b/playground/src/components/UnsupportedChainModal.tsx new file mode 100644 index 000000000..1df3110fd --- /dev/null +++ b/playground/src/components/UnsupportedChainModal.tsx @@ -0,0 +1,41 @@ +import { AlertTriangle } from 'lucide-react'; +import type { Blockchain } from '@yellow-org/sdk'; + +interface Props { + chains: Blockchain[]; + onSwitchChain: (chainId: bigint) => void; +} + +export default function UnsupportedChainModal({ chains, onSwitchChain }: Props) { + return ( +
+
+
+ +
+

Unsupported network

+

+ Your wallet is on a chain Nitrolite doesn't support yet. Switch to one of the supported networks below to + continue. +

+
+ {chains.length === 0 ? ( +

No supported chains discovered yet.

+ ) : ( + chains.map(c => ( + + )) + )} +
+
+
+ ); +} diff --git a/playground/src/components/WalletBar.tsx b/playground/src/components/WalletBar.tsx new file mode 100644 index 000000000..e7574c8a6 --- /dev/null +++ b/playground/src/components/WalletBar.tsx @@ -0,0 +1,171 @@ +import { useEffect, useState } from 'react'; +import { Key, X, FlaskConical } from 'lucide-react'; +import type { Address } from 'viem'; +import type { Blockchain } from '@yellow-org/sdk'; +import CopyButton from './CopyButton'; +import { formatAddress, timeAgo } from '../utils'; +import type { StoredSessionKey } from '../sessionKey'; +import { secondsUntilExpiry } from '../sessionKey'; +import { chainDisplayName } from '../chainMeta'; + +export type AppTab = 'main' | 'history' | 'keys'; + +interface Props { + address: Address | null; + chainId: bigint | null; + chains: Blockchain[]; + lastCommsAt: Date | null; + nodeError: string | null; + isConnecting: boolean; + sessionKey: StoredSessionKey | null; + activeTab: AppTab; + onConnect: () => void; + onDisconnect: () => void; + onSwitchChain: (chainId: bigint) => void; + onClearSessionKey: () => void; + onTabChange: (tab: AppTab) => void; +} + +export default function WalletBar({ + address, + chainId, + chains, + lastCommsAt, + nodeError, + isConnecting, + sessionKey, + activeTab, + onConnect, + onDisconnect, + onSwitchChain, + onClearSessionKey, + onTabChange, +}: Props) { + const [, setTick] = useState(0); + useEffect(() => { + const id = setInterval(() => setTick(t => t + 1), 1000); + return () => clearInterval(id); + }, []); + + const currentChain = chains.find(c => c.id === chainId); + const currentChainName = currentChain ? chainDisplayName(currentChain.id, currentChain.name) : undefined; + const sortedChains = [...chains].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + + return ( + + ); +} + +function formatSkExpiry(sk: StoredSessionKey): string { + const sec = secondsUntilExpiry(sk); + if (sec <= 0) return 'expired'; + if (sec < 60) return `${sec}s`; + if (sec < 3600) return `${Math.floor(sec / 60)}m`; + return `${Math.floor(sec / 3600)}h`; +} diff --git a/playground/src/config.ts b/playground/src/config.ts new file mode 100644 index 000000000..b9d7e8b29 --- /dev/null +++ b/playground/src/config.ts @@ -0,0 +1,47 @@ +// Runtime configuration resolved once at module load. +// +// Production builds load /v1/playground/env.js (rendered by the container +// entrypoint via envsubst) which sets `window.__ENV__`. Vite dev can override +// via `VITE_*` in `.env.local`. The hard-coded fallback keeps `npm run dev` +// working out of the box against sandbox. + +const NODE_URL_FALLBACK = 'wss://nitronode-sandbox.yellow.org/v1/ws'; + +export const NODE_URL: string = + (typeof window !== 'undefined' && window.__ENV__?.NITRONODE_URL) || + import.meta.env.VITE_NITRONODE_URL || + NODE_URL_FALLBACK; + +// Faucet host defaults to the nitronode host on the matching HTTP scheme, +// since the chart deploys both behind the same ingress. An explicit override +// is honored first so a split-host deploy can point the faucet elsewhere. +function deriveFaucetUrl(nodeUrl: string): string { + try { + const u = new URL(nodeUrl); + u.protocol = u.protocol === 'wss:' ? 'https:' : 'http:'; + u.pathname = '/v1/faucet-app/requestTokens'; + u.search = ''; + u.hash = ''; + return u.toString(); + } catch { + return 'https://nitronode-sandbox.yellow.org/v1/faucet-app/requestTokens'; + } +} + +export const FAUCET_URL: string = + (typeof window !== 'undefined' && window.__ENV__?.FAUCET_URL) || + import.meta.env.VITE_FAUCET_URL || + deriveFaucetUrl(NODE_URL); + +// Whether the faucet UI surface is enabled. The faucet itself ships only to +// non-prod envs (see helmfile.yaml.gotmpl), so this flag hides the tab on +// hosts where the request would 404. Default `true` keeps sandbox/stress +// behavior — prod overrides via env.js. +function parseEnabled(raw: string | undefined): boolean | null { + if (raw === undefined || raw === '') return null; + return raw !== 'false' && raw !== '0'; +} +export const FAUCET_ENABLED: boolean = + parseEnabled(typeof window !== 'undefined' ? window.__ENV__?.FAUCET_ENABLED : undefined) ?? + parseEnabled(import.meta.env.VITE_FAUCET_ENABLED) ?? + true; diff --git a/playground/src/hooks/useChannelOps.tsx b/playground/src/hooks/useChannelOps.tsx new file mode 100644 index 000000000..3df78bce2 --- /dev/null +++ b/playground/src/hooks/useChannelOps.tsx @@ -0,0 +1,297 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; +import { showErrorToast } from "../toastError"; +import type { Client, Asset } from '@yellow-org/sdk'; +import { TransitionType } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address, Hash } from 'viem'; +import { createPublicClient, http } from 'viem'; +import { rpcUrlFor } from '../networks'; + +interface PendingTransfer { + to: Address; + asset: string; + amount: Decimal; +} + +export type DepositPhase = 'idle' | 'approving' | 'signing_state' | 'signing_tx' | 'confirming'; +export type WithdrawPhase = 'idle' | 'signing_state' | 'signing_tx' | 'confirming'; +export type TransferPhase = 'idle' | 'signing_state'; + +export interface UseChannelOpsResult { + deposit: (blockchainId: bigint, asset: string, amount: Decimal) => Promise; + withdraw: (blockchainId: bigint, asset: string, amount: Decimal) => Promise; + transfer: (to: Address, asset: string, amount: Decimal) => Promise; + closeChannel: (asset: string, blockchainId: bigint) => Promise; + depositPhase: DepositPhase; + withdrawPhase: WithdrawPhase; + transferPhase: TransferPhase; + needsApproval: boolean | null; + checkDepositAllowance: (blockchainId: bigint, asset: string, amount: Decimal) => Promise; + closingAsset: string | null; + homechainModalAsset: string | null; + onHomechainSelected: (asset: string, chainId: bigint) => Promise; + onHomechainModalDismiss: () => void; +} + +export function useChannelOps( + client: Client | null, + address: Address | null, + supportedAssets: Asset[], + onAfterOp?: () => void, + onAfterTxMined?: () => void, +): UseChannelOpsResult { + const [depositPhase, setDepositPhase] = useState('idle'); + const [withdrawPhase, setWithdrawPhase] = useState('idle'); + const [transferPhase, setTransferPhase] = useState('idle'); + const [needsApproval, setNeedsApproval] = useState(null); + const [closingAsset, setClosingAsset] = useState(null); + const [homechainModalAsset, setHomechainModalAsset] = useState(null); + const pendingTransferRef = useRef(null); + + const generationRef = useRef(0); + useEffect(() => { + generationRef.current += 1; + }, [address]); + + const tokenInfoFor = useCallback( + (asset: string, chainId: bigint): { address: string; decimals: number } | undefined => { + const a = supportedAssets.find(x => x.symbol === asset); + const token = a?.tokens.find(t => t.blockchainId === chainId); + if (!token) return undefined; + return { address: token.address, decimals: token.decimals }; + }, + [supportedAssets], + ); + + const checkDepositAllowance = useCallback( + async (blockchainId: bigint, asset: string, amount: Decimal) => { + if (!client || !address || amount.lte(0)) { setNeedsApproval(null); return; } + try { + const tokenInfo = tokenInfoFor(asset, blockchainId); + if (!tokenInfo || /^0x0+$/i.test(tokenInfo.address)) { setNeedsApproval(false); return; } + const allowance = await client.checkTokenAllowance(blockchainId, tokenInfo.address, address); + const amountUnits = BigInt( + amount.mul(new Decimal(10).pow(tokenInfo.decimals)).floor().toFixed(0), + ); + setNeedsApproval(allowance < amountUnits); + } catch { + setNeedsApproval(null); + } + }, + [client, address, tokenInfoFor], + ); + + const handleError = (err: unknown, label: string) => { + // EIP-1193 user rejection: code 4001 + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) { + toast('Transaction cancelled'); + return; + } + showErrorToast(`${label} failed: ${e?.message ?? String(err)}`); + }; + + const deposit = useCallback( + async (blockchainId: bigint, asset: string, amount: Decimal) => { + if (!client || !address) return; + const gen = generationRef.current; + setDepositPhase('approving'); // start — shows "Approve" or transitions to approval immediately + try { + const tokenInfo = tokenInfoFor(asset, blockchainId); + if (!tokenInfo) throw new Error(`token not found for ${asset} on chain ${blockchainId}`); + + const isNative = /^0x0+$/i.test(tokenInfo.address); + if (!isNative) { + const allowance = await client.checkTokenAllowance(blockchainId, tokenInfo.address, address); + if (generationRef.current !== gen) { + toast('Wallet changed — operation cancelled'); + return; + } + const amountUnits = BigInt( + amount.mul(new Decimal(10).pow(tokenInfo.decimals)).floor().toFixed(0), + ); + if (allowance < amountUnits) { + // Approve effectively-unlimited once so future deposits skip the popup. + const maxUnits = (1n << 256n) - 1n; + const divisor = 10n ** BigInt(tokenInfo.decimals); + const humanMax = new Decimal((maxUnits / divisor).toString()); + await client.approveToken(blockchainId, asset, humanMax); + setNeedsApproval(false); + if (generationRef.current !== gen) { + toast('Wallet changed — operation cancelled'); + return; + } + } + } + + setDepositPhase('signing_state'); + await client.deposit(blockchainId, asset, amount); + if (generationRef.current !== gen) return; + onAfterTxMined?.(); // signed/issued states update; unified balance stays until tx mines + setDepositPhase('signing_tx'); + const depositTxHash = await client.checkpoint(asset); + if (generationRef.current !== gen) return; + setDepositPhase('confirming'); + const depositRpcUrl = rpcUrlFor(blockchainId); + if (depositRpcUrl && depositTxHash) { + const depositClient = createPublicClient({ transport: http(depositRpcUrl) }); + await depositClient.waitForTransactionReceipt({ hash: depositTxHash as Hash }); + if (generationRef.current !== gen) return; + } + toast.success(`Deposited ${amount.toString()} ${asset}`); + onAfterOp?.(); // updates unified balance and on-chain balance + onAfterTxMined?.(); // forces channel-states refresh (enforced version, checkpoint button) + } catch (err) { + handleError(err, 'Deposit'); + } finally { + setDepositPhase('idle'); + } + }, + [client, address, tokenInfoFor, onAfterOp, onAfterTxMined], + ); + + const withdraw = useCallback( + async (blockchainId: bigint, asset: string, amount: Decimal) => { + if (!client || !address) return; + const gen = generationRef.current; + setWithdrawPhase('signing_state'); + try { + await client.withdraw(blockchainId, asset, amount); + if (generationRef.current !== gen) return; + onAfterOp?.(); // unified balance updates immediately after state is signed + onAfterTxMined?.(); // signed/issued states update + setWithdrawPhase('signing_tx'); + const withdrawTxHash = await client.checkpoint(asset); + if (generationRef.current !== gen) return; + setWithdrawPhase('confirming'); + const withdrawRpcUrl = rpcUrlFor(blockchainId); + if (withdrawRpcUrl && withdrawTxHash) { + const withdrawClient = createPublicClient({ transport: http(withdrawRpcUrl) }); + await withdrawClient.waitForTransactionReceipt({ hash: withdrawTxHash as Hash }); + if (generationRef.current !== gen) return; + } + toast.success(`Withdrew ${amount.toString()} ${asset}`); + onAfterOp?.(); // updates unified balance and on-chain balance + onAfterTxMined?.(); // forces channel-states refresh (enforced version, checkpoint button) + } catch (err) { + handleError(err, 'Withdraw'); + } finally { + setWithdrawPhase('idle'); + } + }, + [client, address, onAfterOp, onAfterTxMined], + ); + + const performTransfer = useCallback( + async (to: Address, asset: string, amount: Decimal): Promise => { + if (!client) return false; + const gen = generationRef.current; + setTransferPhase('signing_state'); + try { + await client.transfer(to, asset, amount); + if (generationRef.current !== gen) return false; + toast.success(`Transferred ${amount.toString()} ${asset}`); + onAfterOp?.(); + return true; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + // Heuristic: no home blockchain configured for this asset. + if (msg.toLowerCase().includes('home blockchain') && msg.toLowerCase().includes('not set')) { + pendingTransferRef.current = { to, asset, amount }; + setHomechainModalAsset(asset); + return false; + } + handleError(err, 'Transfer'); + return false; + } finally { + setTransferPhase('idle'); + } + }, + [client, onAfterOp], + ); + + const transfer = useCallback( + async (to: Address, asset: string, amount: Decimal) => { + await performTransfer(to, asset, amount); + }, + [performTransfer], + ); + + const onHomechainSelected = useCallback( + async (asset: string, chainId: bigint) => { + if (!client) return; + try { + await client.setHomeBlockchain(asset, chainId); + toast.success(`Home chain set for ${asset}`); + const pending = pendingTransferRef.current; + pendingTransferRef.current = null; + setHomechainModalAsset(null); + if (pending) { + await performTransfer(pending.to, pending.asset, pending.amount); + } + } catch (err) { + handleError(err, 'Set home chain'); + } + }, + [client, performTransfer], + ); + + const onHomechainModalDismiss = useCallback(() => { + pendingTransferRef.current = null; + setHomechainModalAsset(null); + }, []); + + const closeChannel = useCallback( + async (asset: string, blockchainId: bigint) => { + if (!client || !address) return; + const gen = generationRef.current; + setClosingAsset(asset); + try { + toast('Closing channel…'); + // If a Finalize state is already signed (e.g. a previous checkpoint tx failed or + // the channel is in Closing status on-chain), skip re-signing and go straight to + // the on-chain transaction. + const signedState = await client.getLatestState(address, asset, true); + if (generationRef.current !== gen) return; + if (!signedState || signedState.transition.type !== TransitionType.Finalize) { + await client.closeHomeChannel(asset); + if (generationRef.current !== gen) return; + } + const txHash = await client.checkpoint(asset); + if (generationRef.current !== gen) return; + // Wait for the transaction to be mined before reporting success. + const rpcUrl = rpcUrlFor(blockchainId); + if (rpcUrl && txHash) { + const publicClient = createPublicClient({ transport: http(rpcUrl) }); + await publicClient.waitForTransactionReceipt({ hash: txHash as Hash }); + if (generationRef.current !== gen) return; + } + toast.success(`Closed channel for ${asset}`); + onAfterOp?.(); + onAfterTxMined?.(); + } catch (err) { + handleError(err, 'Close'); + } finally { + setClosingAsset(null); + } + }, + [client, address, onAfterOp, onAfterTxMined], + ); + + return { + deposit, + withdraw, + transfer, + closeChannel, + depositPhase, + withdrawPhase, + transferPhase, + needsApproval, + checkDepositAllowance, + closingAsset, + homechainModalAsset, + onHomechainSelected, + onHomechainModalDismiss, + }; +} diff --git a/playground/src/hooks/useChannelStates.ts b/playground/src/hooks/useChannelStates.ts new file mode 100644 index 000000000..90eb08290 --- /dev/null +++ b/playground/src/hooks/useChannelStates.ts @@ -0,0 +1,166 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; +import type { Client, State, Channel } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address, Hash } from 'viem'; +import { createPublicClient, http } from 'viem'; +import { showErrorToast } from '../toastError'; +import { rpcUrlFor } from '../networks'; + +export interface EnforcedState { + stateVersion: bigint; + amount: Decimal; +} + +export interface UseChannelStatesResult { + enforced: EnforcedState | null; + signed: State | null; + issued: State | null; + isLoading: boolean; + error: string | null; + refresh: () => Promise; + canAcknowledge: boolean; + canCheckpoint: boolean; + acknowledge: () => Promise; + checkpoint: () => Promise; + isAcknowledging: boolean; + isCheckpointing: boolean; +} + +export function useChannelStates( + client: Client | null, + address: Address | null, + asset: string, + enforcedBalance: Decimal | null | undefined, + onAfterOp?: () => void, + refreshKey?: number, +): UseChannelStatesResult { + const [enforced, setEnforced] = useState(null); + const [signed, setSigned] = useState(null); + const [issued, setIssued] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [isAcknowledging, setIsAcknowledging] = useState(false); + const [isCheckpointing, setIsCheckpointing] = useState(false); + + // Track the last on-chain version we recorded so the enforced balance only + // updates when a new checkpoint lands, not on every off-chain state change. + const lastEnforcedVersionRef = useRef(null); + const lastEnforcedAmountRef = useRef(null); + // Keep a ref to the prop so refresh() can read it without being recreated on + // every enforcedBalance change. + const enforcedBalancePropRef = useRef(enforcedBalance); + useEffect(() => { enforcedBalancePropRef.current = enforcedBalance; }, [enforcedBalance]); + + const refresh = useCallback(async () => { + if (!client || !address) { + setEnforced(null); + setSigned(null); + setIssued(null); + return; + } + setIsLoading(true); + setError(null); + try { + const [homeChannel, signedState, issuedState] = await Promise.all([ + client.getHomeChannel(address, asset) as Promise, + client.getLatestState(address, asset, true), + client.getLatestState(address, asset, false), + ]); + setSigned(signedState); + setIssued(issuedState); + if (homeChannel && homeChannel.stateVersion > 0n) { + const v = homeChannel.stateVersion; + if (lastEnforcedVersionRef.current !== v) { + // On-chain version changed — a checkpoint was written. Record the new + // enforced balance from the matching signed state if available, otherwise + // fall back to the prop (covers the edge case of first load with + // unconfirmed states already present). + lastEnforcedVersionRef.current = v; + lastEnforcedAmountRef.current = + signedState?.version === v + ? signedState.homeLedger.userBalance + : (enforcedBalancePropRef.current ?? null); + } + // Use the cached amount so off-chain state changes (transfers, etc.) + // never overwrite the enforced row until the next checkpoint. + setEnforced({ stateVersion: v, amount: lastEnforcedAmountRef.current ?? new Decimal(0) }); + } else { + setEnforced(null); + } + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setIsLoading(false); + } + }, [client, address, asset, enforcedBalance]); // enforcedBalance triggers re-fetch; actual enforced amount is guarded by refs + + useEffect(() => { + refresh(); + }, [refresh, refreshKey]); + + const canAcknowledge = !!issued && (signed === null || issued.version > signed.version); + const canCheckpoint = !!signed && (enforced === null || signed.version > enforced.stateVersion); + + const handleOpError = (err: unknown, label: string) => { + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) { + toast('Transaction cancelled'); + return; + } + showErrorToast(`${label} failed: ${e?.message ?? String(err)}`); + }; + + const acknowledge = useCallback(async () => { + if (!client) return; + setIsAcknowledging(true); + try { + await client.acknowledge(asset); + await refresh(); + onAfterOp?.(); + } catch (err) { + handleOpError(err, 'Acknowledge'); + } finally { + setIsAcknowledging(false); + } + }, [client, asset, refresh, onAfterOp]); + + const checkpoint = useCallback(async () => { + if (!client) return; + setIsCheckpointing(true); + try { + const txHash = await client.checkpoint(asset); + // Wait for the tx to be mined so the enforced state and on-chain balance + // reflect the checkpoint when we refresh. + const blockchainId = signed?.homeLedger.blockchainId; + if (blockchainId && txHash) { + const rpcUrl = rpcUrlFor(blockchainId); + if (rpcUrl) { + const publicClient = createPublicClient({ transport: http(rpcUrl) }); + await publicClient.waitForTransactionReceipt({ hash: txHash as Hash }); + } + } + await refresh(); + onAfterOp?.(); + } catch (err) { + handleOpError(err, 'Checkpoint'); + } finally { + setIsCheckpointing(false); + } + }, [client, asset, signed, refresh, onAfterOp]); + + return { + enforced, + signed, + issued, + isLoading, + error, + refresh, + canAcknowledge, + canCheckpoint, + acknowledge, + checkpoint, + isAcknowledging, + isCheckpointing, + }; +} diff --git a/playground/src/hooks/useChannels.ts b/playground/src/hooks/useChannels.ts new file mode 100644 index 000000000..25405fe36 --- /dev/null +++ b/playground/src/hooks/useChannels.ts @@ -0,0 +1,44 @@ +import { useCallback, useEffect, useState } from 'react'; +import type { Client, Channel } from '@yellow-org/sdk'; +import type { Address } from 'viem'; + +export interface UseChannelsResult { + channels: Channel[]; + isLoading: boolean; + error: string | null; + refresh: () => Promise; +} + +export function useChannels(client: Client | null, address: Address | null): UseChannelsResult { + const [channels, setChannels] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const refresh = useCallback(async () => { + if (!client || !address) { + setChannels([]); + return; + } + setIsLoading(true); + setError(null); + try { + const res = await client.getChannels(address); + if (res.metadata && res.metadata.totalCount > res.channels.length) { + console.warn( + `[playground] getChannels returned ${res.channels.length} of ${res.metadata.totalCount} total — pagination not implemented`, + ); + } + setChannels(res.channels); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setIsLoading(false); + } + }, [client, address]); + + useEffect(() => { + refresh(); + }, [refresh]); + + return { channels, isLoading, error, refresh }; +} diff --git a/playground/src/hooks/useNitrolite.ts b/playground/src/hooks/useNitrolite.ts new file mode 100644 index 000000000..91276e452 --- /dev/null +++ b/playground/src/hooks/useNitrolite.ts @@ -0,0 +1,273 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + Client, + ChannelDefaultSigner, + withBlockchainRPC, + type StateSigner, + type TransactionSigner, +} from '@yellow-org/sdk'; +import type { Asset, Blockchain } from '@yellow-org/sdk'; +import { Decimal } from 'decimal.js'; +import type { Address, WalletClient } from 'viem'; +import { WalletStateSigner, WalletTransactionSigner } from '../walletSigners'; +import { rpcUrlFor } from '../networks'; +import { buildSessionKeyStateSigner, type StoredSessionKey } from '../sessionKey'; +import { NODE_URL } from '../config'; + +export interface UseNitroliteResult { + client: Client | null; + isConnecting: boolean; + isConnected: boolean; + lastCommsAt: Date | null; + nodeError: string | null; + supportedAssets: Asset[]; + supportedChains: Blockchain[]; + balances: Record; + onChainBalances: Record; + isSessionKeyActive: boolean; + refresh: () => Promise; + touch: () => void; +} + +export function useNitrolite( + address: Address | null, + walletClient: WalletClient | null, + sessionKey: StoredSessionKey | null, + currentChainId?: bigint | null, +): UseNitroliteResult { + const [client, setClient] = useState(null); + const [isConnecting, setIsConnecting] = useState(false); + const [isConnected, setIsConnected] = useState(false); + const [lastCommsAt, setLastCommsAt] = useState(null); + const [nodeError, setNodeError] = useState(null); + const [supportedAssets, setSupportedAssets] = useState([]); + const [supportedChains, setSupportedChains] = useState([]); + const [balances, setBalances] = useState>({}); + const [onChainBalances, setOnChainBalances] = useState>({}); + + const clientRef = useRef(null); + const assetsRef = useRef([]); + const currentChainIdRef = useRef(currentChainId ?? null); + useEffect(() => { currentChainIdRef.current = currentChainId ?? null; }, [currentChainId]); + + // Stored factory that creates a final Client with the pre-computed RPC options. + // Used by the fast-path rebuild (signer-only change) to skip the probe phase. + const buildClientRef = useRef<((s: StateSigner, t: TransactionSigner) => Promise) | null>(null); + // Track previous identity so we can detect signer-only changes. + const prevAddressRef = useRef
(null); + const prevWalletClientRef = useRef(null); + + const touch = useCallback(() => setLastCommsAt(new Date()), []); + + const balanceChainFor = useCallback((a: Asset): bigint => { + const cid = currentChainIdRef.current; + if (cid != null && a.tokens.some(t => t.blockchainId === cid)) return cid; + return a.suggestedBlockchainId; + }, []); + + const refresh = useCallback(async () => { + const c = clientRef.current; + if (!c || !address) return; + try { + const entries = await c.getBalances(address); + const next: Record = {}; + for (const e of entries) next[e.asset] = e.balance; + setBalances(next); + touch(); + + const assets = assetsRef.current; + const ocb: Record = {}; + await Promise.all( + assets.map(async a => { + try { + const bal = await c.getOnChainBalance(balanceChainFor(a), a.symbol, address); + ocb[a.symbol] = bal; + } catch { + ocb[a.symbol] = null; + } + }), + ); + setOnChainBalances(prev => ({ ...prev, ...ocb })); + } catch (err) { + setNodeError(err instanceof Error ? err.message : String(err)); + } + }, [address, touch, balanceChainFor]); + + useEffect(() => { + if (!address || !walletClient) { + // Teardown + buildClientRef.current = null; + prevAddressRef.current = null; + prevWalletClientRef.current = null; + const prev = clientRef.current; + clientRef.current = null; + setClient(null); + setIsConnected(false); + if (prev) prev.close().catch(() => {}); + return; + } + + // Detect what changed this run. + const addressChanged = address !== prevAddressRef.current; + const walletClientChanged = walletClient !== prevWalletClientRef.current; + prevAddressRef.current = address; + prevWalletClientRef.current = walletClient; + + // Build signer for the new session-key state (shared between both paths). + let stateSigner: StateSigner; + if (sessionKey && sessionKey.walletAddress.toLowerCase() === address.toLowerCase()) { + stateSigner = buildSessionKeyStateSigner(sessionKey); + } else { + const walletSigner = new WalletStateSigner(walletClient) as unknown as StateSigner; + stateSigner = new ChannelDefaultSigner(walletSigner); + } + const txSigner = new WalletTransactionSigner(walletClient) as unknown as TransactionSigner; + + // ── Fast path: only the signer changed ─────────────────────────────────── + // Skip probe, config fetch, and asset fetch. Swap the client silently + // without touching isConnecting / isConnected / chains / assets / balances. + if (!addressChanged && !walletClientChanged && buildClientRef.current) { + let cancelled = false; + const prev = clientRef.current; + + buildClientRef.current(stateSigner, txSigner).then(async newClient => { + if (cancelled) { await newClient.close().catch(() => {}); return; } + clientRef.current = newClient; + setClient(newClient); + touch(); + if (prev) await prev.close().catch(() => {}); + }).catch(err => { + if (!cancelled) setNodeError(err instanceof Error ? err.message : String(err)); + }); + + return () => { cancelled = true; }; + } + + // ── Full rebuild: address or walletClient changed ───────────────────────── + let cancelled = false; + setIsConnecting(true); + setNodeError(null); + + const timer = setTimeout(async () => { + const prev = clientRef.current; + clientRef.current = null; + if (prev) { + try { await prev.close(); } catch { /* ignore */ } + } + + try { + const probe = await Client.create(NODE_URL, stateSigner, txSigner); + if (cancelled) { await probe.close().catch(() => {}); return; } + + const cfg = await probe.getConfig(); + const chains = cfg.blockchains; + const assets = await probe.getAssets(); + if (cancelled) { await probe.close().catch(() => {}); return; } + await probe.close().catch(() => {}); + + const opts = chains + .map(c => rpcUrlFor(c.id)) + .map((url, i) => (url ? withBlockchainRPC(chains[i].id, url) : null)) + .filter((o): o is NonNullable => o !== null); + + // Store the builder so signer-only changes can reuse it. + buildClientRef.current = (s, t) => Client.create(NODE_URL, s, t, ...opts); + + const finalClient = await buildClientRef.current(stateSigner, txSigner); + if (cancelled) { await finalClient.close().catch(() => {}); return; } + + clientRef.current = finalClient; + setClient(finalClient); + assetsRef.current = assets; + setSupportedAssets(assets); + setSupportedChains(chains); + setIsConnected(true); + setIsConnecting(false); + touch(); + + const entries = await finalClient.getBalances(address); + const nextBal: Record = {}; + for (const e of entries) nextBal[e.asset] = e.balance; + if (!cancelled) { + setBalances(nextBal); + touch(); + + const ocb: Record = {}; + await Promise.all( + assets.map(async a => { + try { + const bal = await finalClient.getOnChainBalance(balanceChainFor(a), a.symbol, address); + ocb[a.symbol] = bal; + } catch { + ocb[a.symbol] = null; + } + }), + ); + if (!cancelled) setOnChainBalances(ocb); + } + } catch (err) { + if (!cancelled) { + setNodeError(err instanceof Error ? err.message : String(err)); + setIsConnecting(false); + setIsConnected(false); + } + } + }, 200); + + return () => { + cancelled = true; + clearTimeout(timer); + }; + }, [address, walletClient, sessionKey, touch]); + + useEffect(() => { + const c = clientRef.current; + const assets = assetsRef.current; + if (!c || !address || !assets.length || currentChainId == null) return; + const ocb: Record = {}; + Promise.all( + assets.map(async a => { + if (!a.tokens.some(t => t.blockchainId === currentChainId)) return; + try { + ocb[a.symbol] = await c.getOnChainBalance(currentChainId, a.symbol, address); + } catch { + ocb[a.symbol] = null; + } + }), + ).then(() => setOnChainBalances(prev => ({ ...prev, ...ocb }))).catch(() => {}); + }, [address, currentChainId]); + + useEffect(() => { + return () => { + const prev = clientRef.current; + if (prev) prev.close().catch(() => {}); + }; + }, []); + + const stableBalances = useMemo(() => balances, [JSON.stringify(serializable(balances))]); + const stableOnChain = useMemo(() => onChainBalances, [JSON.stringify(serializable(onChainBalances))]); + + const isSessionKeyActive = + !!sessionKey && !!address && sessionKey.walletAddress.toLowerCase() === address.toLowerCase(); + + return { + client, + isConnecting, + isConnected, + lastCommsAt, + nodeError, + supportedAssets, + supportedChains, + balances: stableBalances, + onChainBalances: stableOnChain, + isSessionKeyActive, + refresh, + touch, + }; +} + +function serializable(rec: Record): Record { + const out: Record = {}; + for (const [k, v] of Object.entries(rec)) out[k] = v ? v.toString() : null; + return out; +} diff --git a/playground/src/hooks/useSessionKey.ts b/playground/src/hooks/useSessionKey.ts new file mode 100644 index 000000000..b17ef930a --- /dev/null +++ b/playground/src/hooks/useSessionKey.ts @@ -0,0 +1,116 @@ +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'sonner'; +import { showErrorToast } from "../toastError"; +import type { Address } from 'viem'; +import type { Client } from '@yellow-org/sdk'; +import { + loadSessionKey, + loadAllSessionKeys, + saveSessionKey, + clearSessionKey, + selectKeyInStorage, + registerSessionKey, + type StoredSessionKey, +} from '../sessionKey'; +import { NODE_URL } from '../config'; + +export interface UseSessionKeyResult { + sessionKey: StoredSessionKey | null; + allKeys: StoredSessionKey[]; + isRegistering: boolean; + register: (client: Client, assetSymbols: string[], expiresAt?: bigint) => Promise; + selectKey: (sessionKeyAddress: Address) => void; + clear: () => void; + refreshAllKeys: () => void; +} + +/** + * Owns the local session-key state. `register` is supplied the SDK Client at + * call time because the same Client must be used for signing (it must be the + * wallet-backed one — registering while a session-key client is active would + * cause the SK to sign its own authorization, which the node rejects). + */ +export function useSessionKey(address: Address | null): UseSessionKeyResult { + const [sessionKey, setSessionKey] = useState(null); + const [isRegistering, setIsRegistering] = useState(false); + const [allKeys, setAllKeys] = useState([]); + + // Load on address change. loadSessionKey clears expired entries internally. + useEffect(() => { + if (!address) { + setSessionKey(null); + setAllKeys([]); + return; + } + setSessionKey(loadSessionKey(NODE_URL, address)); + setAllKeys(loadAllSessionKeys(NODE_URL, address)); + }, [address]); + + // Re-check expiry once a minute so the chip can flip to "expired"/banner can + // re-appear without requiring a page reload. + useEffect(() => { + if (!address) return; + const id = setInterval(() => { + const fresh = loadSessionKey(NODE_URL, address); + setSessionKey(prev => { + if (prev?.privateKey === fresh?.privateKey) return prev; + return fresh; + }); + setAllKeys(loadAllSessionKeys(NODE_URL, address)); + }, 60_000); + return () => clearInterval(id); + }, [address]); + + const register = useCallback( + async (client: Client, assetSymbols: string[], expiresAt?: bigint) => { + if (!address || !assetSymbols.length) { + showErrorToast('Cannot register: missing address or assets'); + return; + } + setIsRegistering(true); + try { + const sk = await registerSessionKey({ + client, + walletAddress: address, + assets: assetSymbols, + nextVersion: 1n, + expiresAt, + }); + saveSessionKey(NODE_URL, sk); + setSessionKey(sk); + setAllKeys(loadAllSessionKeys(NODE_URL, address)); + toast.success('Session key active — state ops will no longer prompt MetaMask'); + } catch (err) { + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) toast('Cancelled'); + else showErrorToast(`Session key setup failed: ${e?.message ?? String(err)}`); + } finally { + setIsRegistering(false); + } + }, + [address], + ); + + const selectKey = useCallback((sessionKeyAddress: Address) => { + if (!address) return; + const selected = selectKeyInStorage(NODE_URL, address, sessionKeyAddress); + if (selected) { + setSessionKey(selected); + setAllKeys(loadAllSessionKeys(NODE_URL, address)); + toast(`Switched to session key ${sessionKeyAddress.slice(0, 6)}…${sessionKeyAddress.slice(-4)}`); + } + }, [address]); + + const clear = useCallback(() => { + if (address) clearSessionKey(NODE_URL, address); + setSessionKey(null); + if (address) setAllKeys(loadAllSessionKeys(NODE_URL, address)); + toast('Session key cleared'); + }, [address]); + + const refreshAllKeys = useCallback(() => { + if (address) setAllKeys(loadAllSessionKeys(NODE_URL, address)); + }, [address]); + + return { sessionKey, allKeys, isRegistering, register, selectKey, clear, refreshAllKeys }; +} diff --git a/playground/src/hooks/useSessionKeyManagement.ts b/playground/src/hooks/useSessionKeyManagement.ts new file mode 100644 index 000000000..ed01aef51 --- /dev/null +++ b/playground/src/hooks/useSessionKeyManagement.ts @@ -0,0 +1,155 @@ +import { useState, useCallback } from 'react'; +import { toast } from 'sonner'; +import { showErrorToast } from '../toastError'; +import type { Address, WalletClient } from 'viem'; +import { + Client, + ChannelDefaultSigner, + type StateSigner, + type TransactionSigner, + type ChannelSessionKeyStateV1, +} from '@yellow-org/sdk'; +import { + registerSessionKey, + updateSessionKey, + loadAllSessionKeys, + saveSessionKey, + saveKeyInactive, + type StoredSessionKey, +} from '../sessionKey'; +import { WalletStateSigner, WalletTransactionSigner } from '../walletSigners'; +import { NODE_URL } from '../config'; + +interface UseSessionKeyManagementResult { + serverKeys: ChannelSessionKeyStateV1[]; + isLoading: boolean; + isSubmitting: boolean; + fetchKeys: () => Promise; + register: (walletAddress: Address, assets: string[], expiresAt: bigint) => Promise; + update: (walletAddress: Address, currentKey: ChannelSessionKeyStateV1, assets: string[], expiresAt: bigint) => Promise; + revoke: (walletAddress: Address, currentKey: ChannelSessionKeyStateV1) => Promise; +} + +export function useSessionKeyManagement( + client: Client | null, + address: Address | null, + walletClient: WalletClient | null, +): UseSessionKeyManagementResult { + const [serverKeys, setServerKeys] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + const fetchKeys = useCallback(async () => { + if (!client || !address) return; + setIsLoading(true); + try { + const keys = await client.getLastChannelKeyStates(address, undefined, { includeInactive: true }); + setServerKeys(keys); + } catch (err) { + showErrorToast(`Failed to load session keys: ${(err as Error).message ?? String(err)}`); + } finally { + setIsLoading(false); + } + }, [client, address]); + + // Build a temporary wallet-backed Client so signChannelSessionKeyState always + // uses the 0x00 wallet signer regardless of what the main client's signer is. + const buildWalletOnlyClient = useCallback(async (): Promise => { + if (!walletClient) return null; + try { + const stateSigner = new ChannelDefaultSigner( + new WalletStateSigner(walletClient) as unknown as StateSigner, + ); + const txSigner = new WalletTransactionSigner(walletClient) as unknown as TransactionSigner; + return await Client.create(NODE_URL, stateSigner, txSigner); + } catch { + return null; + } + }, [walletClient]); + + const register = useCallback(async ( + walletAddress: Address, + assets: string[], + expiresAt: bigint, + ): Promise => { + if (!walletClient) { showErrorToast('Wallet not connected'); return null; } + setIsSubmitting(true); + const signingClient = await buildWalletOnlyClient(); + if (!signingClient) { showErrorToast('Could not create signing client'); setIsSubmitting(false); return null; } + try { + const sk = await registerSessionKey({ client: signingClient, walletAddress, assets, nextVersion: 1n, expiresAt }); + saveSessionKey(NODE_URL, sk); + toast.success('Session key registered'); + await fetchKeys(); + return sk; + } catch (err) { + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) toast('Cancelled'); + else showErrorToast(`Registration failed: ${e?.message ?? String(err)}`); + return null; + } finally { + setIsSubmitting(false); + signingClient.close().catch(() => {}); + } + }, [walletClient, buildWalletOnlyClient, fetchKeys]); + + const update = useCallback(async ( + walletAddress: Address, + currentKey: ChannelSessionKeyStateV1, + assets: string[], + expiresAt: bigint, + ): Promise => { + if (!walletClient) { showErrorToast('Wallet not connected'); return null; } + const localKey = loadAllSessionKeys(NODE_URL, walletAddress) + .find(k => k.sessionKeyAddress.toLowerCase() === currentKey.session_key.toLowerCase()); + if (!localKey) { showErrorToast('Session key not found in local storage'); return null; } + setIsSubmitting(true); + const signingClient = await buildWalletOnlyClient(); + if (!signingClient) { showErrorToast('Could not create signing client'); setIsSubmitting(false); return null; } + try { + const sk = await updateSessionKey({ client: signingClient, existingKey: localKey, assets, expiresAt }); + saveSessionKey(NODE_URL, sk); + toast.success('Session key updated'); + await fetchKeys(); + return sk; + } catch (err) { + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) toast('Cancelled'); + else showErrorToast(`Update failed: ${e?.message ?? String(err)}`); + return null; + } finally { + setIsSubmitting(false); + signingClient.close().catch(() => {}); + } + }, [walletClient, buildWalletOnlyClient, fetchKeys]); + + const revoke = useCallback(async ( + walletAddress: Address, + currentKey: ChannelSessionKeyStateV1, + ): Promise => { + if (!walletClient) { showErrorToast('Wallet not connected'); return; } + const localKey = loadAllSessionKeys(NODE_URL, walletAddress) + .find(k => k.sessionKeyAddress.toLowerCase() === currentKey.session_key.toLowerCase()); + if (!localKey) { showErrorToast('Session key not found in local storage'); return; } + setIsSubmitting(true); + const signingClient = await buildWalletOnlyClient(); + if (!signingClient) { showErrorToast('Could not create signing client'); setIsSubmitting(false); return; } + try { + const expiredAt = BigInt(Math.floor(Date.now() / 1000) - 1); + const revokedSk = await updateSessionKey({ client: signingClient, existingKey: localKey, assets: localKey.assets, expiresAt: expiredAt }); + // Persist expired expiresAt to localStorage so allSessionKeys reflects the change immediately. + saveKeyInactive(NODE_URL, revokedSk); + toast.success('Session key revoked'); + await fetchKeys(); + } catch (err) { + const e = err as { code?: number; message?: string }; + if (e?.code === 4001) toast('Cancelled'); + else showErrorToast(`Revoke failed: ${e?.message ?? String(err)}`); + } finally { + setIsSubmitting(false); + signingClient.close().catch(() => {}); + } + }, [walletClient, buildWalletOnlyClient, fetchKeys]); + + return { serverKeys, isLoading, isSubmitting, fetchKeys, register, update, revoke }; +} diff --git a/playground/src/hooks/useWallet.ts b/playground/src/hooks/useWallet.ts new file mode 100644 index 000000000..a0de7656e --- /dev/null +++ b/playground/src/hooks/useWallet.ts @@ -0,0 +1,146 @@ +import { useCallback, useEffect, useState } from 'react'; +import { showErrorToast } from "../toastError"; +import { createWalletClient, custom, type WalletClient, type Address } from 'viem'; + +declare global { + interface Window { + ethereum?: { + request: (args: { method: string; params?: unknown[] }) => Promise; + on?: (event: string, listener: (...args: unknown[]) => void) => void; + removeListener?: (event: string, listener: (...args: unknown[]) => void) => void; + }; + } +} + +export interface WalletState { + address: Address | null; + chainId: bigint | null; + walletClient: WalletClient | null; + isConnecting: boolean; +} + +export interface UseWalletResult extends WalletState { + connect: () => Promise; + disconnect: () => void; + switchChain: (chainId: bigint) => Promise; +} + +export function useWallet(): UseWalletResult { + const [state, setState] = useState({ + address: null, + chainId: null, + walletClient: null, + isConnecting: false, + }); + + const buildClient = useCallback((address: Address): WalletClient => { + return createWalletClient({ + account: address, + transport: custom(window.ethereum!), + }); + }, []); + + const fetchChainId = useCallback(async (): Promise => { + if (!window.ethereum) return null; + const hex = (await window.ethereum.request({ method: 'eth_chainId' })) as string; + return BigInt(hex); + }, []); + + const connect = useCallback(async () => { + if (!window.ethereum) { + showErrorToast('MetaMask not detected. Install the extension to continue.'); + return; + } + setState(s => ({ ...s, isConnecting: true })); + try { + const accounts = (await window.ethereum.request({ method: 'eth_requestAccounts' })) as string[]; + if (!accounts.length) throw new Error('No account returned by wallet'); + const address = accounts[0] as Address; + const chainId = await fetchChainId(); + setState({ + address, + chainId, + walletClient: buildClient(address), + isConnecting: false, + }); + } catch (err) { + setState(s => ({ ...s, isConnecting: false })); + showErrorToast(toErrorMessage(err)); + } + }, [buildClient, fetchChainId]); + + const disconnect = useCallback(() => { + setState({ + address: null, + chainId: null, + walletClient: null, + isConnecting: false, + }); + }, []); + + const switchChain = useCallback(async (chainId: bigint) => { + if (!window.ethereum) return; + const hex = `0x${chainId.toString(16)}`; + try { + await window.ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: hex }] }); + } catch (err) { + showErrorToast(toErrorMessage(err)); + } + }, []); + + useEffect(() => { + if (!window.ethereum) { + showErrorToast('MetaMask not detected. Install the extension to continue.'); + return; + } + + const onAccountsChanged = (...args: unknown[]) => { + const accounts = args[0] as string[]; + if (!accounts || accounts.length === 0) { + disconnect(); + } else { + const address = accounts[0] as Address; + setState(s => ({ ...s, address, walletClient: buildClient(address) })); + } + }; + + const onChainChanged = (...args: unknown[]) => { + const hex = args[0] as string; + setState(s => ({ ...s, chainId: BigInt(hex) })); + }; + + window.ethereum.on?.('accountsChanged', onAccountsChanged); + window.ethereum.on?.('chainChanged', onChainChanged); + + // Probe existing accounts on mount (so a previously connected dapp doesn't require re-click). + window.ethereum + .request({ method: 'eth_accounts' }) + .then(async result => { + const accounts = result as string[]; + if (accounts && accounts.length) { + const address = accounts[0] as Address; + const chainId = await fetchChainId(); + setState({ + address, + chainId, + walletClient: buildClient(address), + isConnecting: false, + }); + } + }) + .catch(() => {}); + + return () => { + window.ethereum?.removeListener?.('accountsChanged', onAccountsChanged); + window.ethereum?.removeListener?.('chainChanged', onChainChanged); + }; + }, [buildClient, disconnect, fetchChainId]); + + return { ...state, connect, disconnect, switchChain }; +} + +function toErrorMessage(err: unknown): string { + if (err instanceof Error) return err.message; + if (err && typeof err === 'object' && 'message' in err) return String((err as { message: unknown }).message); + return String(err); +} diff --git a/playground/src/icons.ts b/playground/src/icons.ts new file mode 100644 index 000000000..790eb255a --- /dev/null +++ b/playground/src/icons.ts @@ -0,0 +1,42 @@ +// Icon paths for tokens and chains. +// Files are stored in public/icons/tokens/ and public/icons/chains/. +// Paths are relative to Vite's `base` (resolved via `import.meta.env.BASE_URL`) +// so the same bundle works both at the dev server root and behind the +// production `/v1/playground/` prefix. Components must handle `img onError` +// with a letter-avatar fallback. + +const BASE = import.meta.env.BASE_URL; + +const TOKEN_ICONS: Record = { + eth: 'icons/tokens/eth.png', + matic: 'icons/tokens/matic.png', + pol: 'icons/tokens/pol.png', + bnb: 'icons/tokens/bnb.png', + usdt: 'icons/tokens/usdt.png', + xrp: 'icons/tokens/xrp.png', + yellow: 'icons/tokens/yellow.png', +}; + +// Testnets reuse their parent mainnet icon. +const CHAIN_ICONS: Record = { + '1': 'icons/chains/ethereum.webp', + '11155111':'icons/chains/ethereum.webp', // Sepolia + '137': 'icons/chains/polygon.webp', + '80002': 'icons/chains/polygon.webp', // Amoy + '8453': 'icons/chains/base.png', + '84532': 'icons/chains/base.png', // Base Sepolia + '59144': 'icons/chains/linea.webp', + '59141': 'icons/chains/linea.webp', // Linea Sepolia + '56': 'icons/chains/binance.webp', + '1449000': 'icons/chains/xrplevm.png', // XRPL EVM Testnet +}; + +export function tokenIconUrl(symbol: string): string | null { + const p = TOKEN_ICONS[symbol.toLowerCase()]; + return p ? BASE + p : null; +} + +export function chainIconUrl(chainId: bigint): string | null { + const p = CHAIN_ICONS[chainId.toString()]; + return p ? BASE + p : null; +} diff --git a/playground/src/index.css b/playground/src/index.css new file mode 100644 index 000000000..4ace73421 --- /dev/null +++ b/playground/src/index.css @@ -0,0 +1,404 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --bg-base: #0a0a0a; + --bg-surface: #141414; + --bg-elevated: #1c1c1c; + --border: #242424; + --text-primary: #f0f0f0; + --text-muted: #666666; + --accent: #f5a623; + --accent-dim: rgba(245, 166, 35, 0.12); + --error: #ef4444; + --success: #22c55e; +} + +*, *::before, *::after { box-sizing: border-box; } + +html, body, #root { + height: 100%; +} + +body { + margin: 0; + font-family: 'Inter', system-ui, sans-serif; + background: var(--bg-base); + color: var(--text-primary); + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +.mono { font-family: 'JetBrains Mono', monospace; } + +/* ── Buttons ── */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 14px; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--bg-elevated); + color: var(--text-primary); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background 0.12s, border-color 0.12s, opacity 0.12s; + font-family: inherit; +} +.btn:hover:not(:disabled) { background: #1f1f1f; } +.btn:disabled { opacity: 0.4; cursor: not-allowed; } + +.btn-primary { + background: var(--accent); + color: #0a0a0a; + border-color: var(--accent); + font-weight: 600; +} +.btn-primary:hover:not(:disabled) { background: #e09520; border-color: #e09520; } +.btn-primary:disabled { background: var(--bg-elevated); color: var(--text-muted); border-color: var(--border); } + +.btn-ghost { + background: transparent; + border: 1px solid var(--border); + color: var(--text-muted); +} +.btn-ghost:hover:not(:disabled) { color: var(--text-primary); border-color: #333; } + +.btn-danger { + background: transparent; + border: none; + color: var(--error); + padding: 6px 10px; +} +.btn-danger:hover:not(:disabled) { background: rgba(239, 68, 68, 0.08); } + +.btn-sm { padding: 5px 10px; font-size: 12px; } + +/* ── Inputs ── */ +.input { + display: flex; + align-items: center; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.12s; +} +.input:focus-within { border-color: var(--accent); } +.input.error { border-color: var(--error); } + +.input input { + flex: 1; + background: transparent; + border: none; + outline: none; + padding: 10px 12px; + font-family: 'JetBrains Mono', monospace; + font-size: 13px; + color: var(--text-primary); + width: 100%; +} +.input input::placeholder { color: var(--text-muted); } +.input input:disabled { opacity: 0.5; cursor: not-allowed; } + +.input-suffix { + padding: 0 12px; + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + color: var(--text-muted); +} + +.input-max { + font-family: 'Inter', sans-serif; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.04em; + color: var(--accent); + background: transparent; + border: none; + cursor: pointer; + padding: 0 12px; +} + +/* ── Card ── */ +.card { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 12px; +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border); +} + +.card-title { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); +} + +/* ── Pills / chips ── */ +.chip { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 8px; + padding: 5px 10px; + font-size: 12px; + color: var(--text-primary); +} + +.dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--success); + display: inline-block; + flex-shrink: 0; +} +.dot.error { background: var(--error); } +.dot.muted { background: var(--text-muted); } + +/* ── Tabs ── */ +.tab { + padding: 7px 16px; + border-radius: 999px; + border: none; + background: transparent; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 500; + color: var(--text-muted); + cursor: pointer; + transition: background 0.12s, color 0.12s; +} +.tab:hover:not(.active) { background: var(--bg-elevated); color: var(--text-primary); } +.tab.active { color: var(--accent); background: var(--accent-dim); } + +/* ── Tooltip ── */ +.tooltip-wrap { position: relative; display: inline-flex; } +.tooltip-wrap .tip { + visibility: hidden; + opacity: 0; + position: absolute; + bottom: calc(100% + 6px); + left: 50%; + transform: translateX(-50%); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text-primary); + font-size: 11px; + padding: 6px 10px; + white-space: normal; + width: max-content; + max-width: min(360px, 80vw); + z-index: 100; + transition: opacity 0.12s; + pointer-events: none; + font-family: 'Inter', sans-serif; + font-weight: 400; +} +.tooltip-wrap:hover .tip { visibility: visible; opacity: 1; } +.help-icon { + font-size: 10px; + color: var(--text-muted); + cursor: default; + border: 1px solid var(--border); + border-radius: 999px; + width: 14px; + height: 14px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 4px; +} + +/* ── Modal ── */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.72); + display: flex; + align-items: center; + justify-content: center; + z-index: 50; + backdrop-filter: blur(4px); +} + +.modal-card { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 12px; + width: 380px; + max-width: calc(100vw - 32px); +} + +/* ── History: quick-filter cell wrapper ── */ +.qf { + position: relative; + display: inline-flex; + align-items: center; + cursor: pointer; + border-radius: 5px; + padding: 2px 4px; + margin: -2px -4px; + transition: background 0.1s; +} +.qf:hover { background: rgba(255,255,255,0.05); } +.qf.qf-on { background: var(--accent-dim); } +.qf.qf-on:hover { background: rgba(245,166,35,0.2); } +.qf::after { + content: attr(data-tip); + position: absolute; + bottom: calc(100% + 6px); + left: 50%; + transform: translateX(-50%); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 9px; + font-size: 11px; + color: var(--text-primary); + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 0.12s; + z-index: 300; +} +.qf:hover::after { opacity: 1; } + +/* ── History: address + copy layout ── */ +.addr-cell { + display: inline-flex; + align-items: center; + gap: 5px; +} +.copy-btn-addr { + background: none; + border: none; + cursor: pointer; + padding: 2px; + color: var(--text-muted); + display: inline-flex; + line-height: 1; + flex-shrink: 0; + transition: color 0.12s, opacity 0.12s; + opacity: 0; +} +.addr-cell:hover .copy-btn-addr { opacity: 1; } +.copy-btn-addr:hover { color: var(--accent); opacity: 1 !important; } + +/* ── History: timestamp format toggle ── */ +.ts-toggle { + position: relative; + cursor: pointer; + display: inline-flex; + align-items: center; +} +.ts-toggle::after { + content: attr(data-tip); + position: absolute; + top: calc(100% + 4px); + left: 0; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 9px; + font-size: 11px; + color: var(--text-primary); + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 0.12s; + z-index: 300; + font-family: 'Inter', sans-serif; +} +.ts-toggle:hover::after { opacity: 1; } +.ts-toggle:hover > * { color: var(--accent); } + +/* ── History: column header filter popovers ── */ +.th-popover-wrap { + position: relative; + display: inline-block; +} +.th-inner { + display: inline-flex; + align-items: center; + gap: 4px; + user-select: none; +} +.th-inner.filterable { cursor: pointer; } +.th-inner.filterable:hover { color: var(--text-primary); } +.th-inner.filter-active { color: var(--accent); } +.th-popover { + position: absolute; + top: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + z-index: 200; + min-width: 170px; + box-shadow: 0 8px 24px rgba(0,0,0,0.5); +} +.th-popover-title { + font-size: 11px; + color: var(--text-muted); + margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.05em; + font-family: 'Inter', sans-serif; +} +.th-popover-actions { + display: flex; + gap: 6px; + margin-top: 10px; + justify-content: flex-end; +} +.th-text-input { + background: var(--bg-base); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 10px; + font-size: 12px; + color: var(--text-primary); + font-family: 'JetBrains Mono', monospace; + outline: none; + width: 100%; + display: block; +} +.th-text-input:focus { border-color: var(--accent); } +.th-text-input::placeholder { color: var(--text-muted); } + +/* ── Scrollbar ── */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: #333; } + +/* ── Spinner ── */ +.spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + diff --git a/playground/src/main.tsx b/playground/src/main.tsx new file mode 100644 index 000000000..9aa52ffd1 --- /dev/null +++ b/playground/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/playground/src/networks.ts b/playground/src/networks.ts new file mode 100644 index 000000000..2ee8e55e2 --- /dev/null +++ b/playground/src/networks.ts @@ -0,0 +1,20 @@ +// Fallback public RPC URLs per chain ID. The Nitronode tells us which chains it supports +// via getConfig(); for each one we need an RPC for the SDK's on-chain reads / writes. +// Until the SDK gains a withEIP1193Provider option, we use these public endpoints. + +export const PUBLIC_RPC_URLS: Record = { + '1': 'https://ethereum-rpc.publicnode.com', + '11155111': 'https://ethereum-sepolia-rpc.publicnode.com', + '137': 'https://polygon-rpc.com', + '80002': 'https://rpc-amoy.polygon.technology', + '8453': 'https://mainnet.base.org', + '84532': 'https://sepolia.base.org', + '42161': 'https://arb1.arbitrum.io/rpc', + '421614': 'https://sepolia-rollup.arbitrum.io/rpc', + '59144': 'https://rpc.linea.build', + '59141': 'https://rpc.sepolia.linea.build', +}; + +export function rpcUrlFor(chainId: bigint): string | undefined { + return PUBLIC_RPC_URLS[chainId.toString()]; +} diff --git a/playground/src/sessionKey.ts b/playground/src/sessionKey.ts new file mode 100644 index 000000000..26a622dc1 --- /dev/null +++ b/playground/src/sessionKey.ts @@ -0,0 +1,215 @@ +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import type { Hex, Address } from 'viem'; +import { + Client, + ChannelSessionKeyStateSigner, + EthereumMsgSigner, + getChannelSessionKeyAuthMetadataHashV1, + type ChannelSessionKeyStateV1, + type StateSigner, +} from '@yellow-org/sdk'; + +const EXPIRY_SECONDS = 24 * 60 * 60; // 24h delegation +const RENEW_BUFFER_SECONDS = 5 * 60; // treat as expired if within this margin + +export interface StoredSessionKey { + privateKey: Hex; + sessionKeyAddress: Address; + walletAddress: Address; + version: string; // bigint stringified + assets: string[]; + expiresAt: string; // unix seconds, bigint stringified + userSig: Hex; // raw EIP-191 (no signer type prefix) + isActive?: boolean; // true = this key is the current signing key +} + +export function storageKeyFor(nodeUrl: string, walletAddress: Address): string { + return `nitrolite_playground_sk_${nodeUrl}::${walletAddress.toLowerCase()}`; +} + +export function isExpired(sk: StoredSessionKey): boolean { + return Number(sk.expiresAt) - RENEW_BUFFER_SECONDS <= Math.floor(Date.now() / 1000); +} + +export function secondsUntilExpiry(sk: StoredSessionKey): number { + return Number(sk.expiresAt) - Math.floor(Date.now() / 1000); +} + +// Internal helper: read the array from localStorage, migrating old single-key format if needed +function loadStoredArray(nodeUrl: string, walletAddress: Address): StoredSessionKey[] { + const key = storageKeyFor(nodeUrl, walletAddress); + const raw = localStorage.getItem(key); + if (!raw) return []; + try { + const parsed = JSON.parse(raw); + // Migration: if old format is a single object (not array), wrap it + if (!Array.isArray(parsed)) { + const single = parsed as StoredSessionKey; + return [{ ...single, isActive: true }]; + } + return parsed as StoredSessionKey[]; + } catch { + return []; + } +} + +// Internal helper: write array back to localStorage +function saveStoredArray(nodeUrl: string, walletAddress: Address, keys: StoredSessionKey[]): void { + localStorage.setItem(storageKeyFor(nodeUrl, walletAddress), JSON.stringify(keys)); +} + +// Load the active (signing) key. Returns null if none or if expired. +export function loadSessionKey(nodeUrl: string, walletAddress: Address): StoredSessionKey | null { + const keys = loadStoredArray(nodeUrl, walletAddress); + const active = keys.find(k => k.isActive); + if (!active) return null; + if (isExpired(active)) { + // Clear the isActive flag but keep the entry for history display + saveStoredArray(nodeUrl, walletAddress, keys.map(k => + k.sessionKeyAddress === active.sessionKeyAddress ? { ...k, isActive: false } : k + )); + return null; + } + return active; +} + +// Load ALL stored keys (including expired/revoked, for display in Session Keys tab) +export function loadAllSessionKeys(nodeUrl: string, walletAddress: Address): StoredSessionKey[] { + return loadStoredArray(nodeUrl, walletAddress); +} + +// Save/update a key in the array (upserts by sessionKeyAddress). Marks it as active. +export function saveSessionKey(nodeUrl: string, sk: StoredSessionKey): void { + const keys = loadStoredArray(nodeUrl, sk.walletAddress); + // Mark all others inactive + const updated = keys + .filter(k => k.sessionKeyAddress !== sk.sessionKeyAddress) + .map(k => ({ ...k, isActive: false })); + updated.push({ ...sk, isActive: true }); + saveStoredArray(nodeUrl, sk.walletAddress, updated); +} + +// Clear the active key flag (keeps the entry in history) — called when user clears +export function clearSessionKey(nodeUrl: string, walletAddress: Address): void { + const keys = loadStoredArray(nodeUrl, walletAddress); + saveStoredArray(nodeUrl, walletAddress, keys.map(k => ({ ...k, isActive: false }))); +} + +// Update a key's stored data (e.g. after revoke, to persist the new expiresAt) without +// marking it as active. Upserts by sessionKeyAddress. +export function saveKeyInactive(nodeUrl: string, sk: StoredSessionKey): void { + const keys = loadStoredArray(nodeUrl, sk.walletAddress); + const rest = keys.filter(k => k.sessionKeyAddress !== sk.sessionKeyAddress); + rest.push({ ...sk, isActive: false }); + saveStoredArray(nodeUrl, sk.walletAddress, rest); +} + +// Select a different key as the active signing key +export function selectKeyInStorage(nodeUrl: string, walletAddress: Address, sessionKeyAddress: Address): StoredSessionKey | null { + const keys = loadStoredArray(nodeUrl, walletAddress); + const target = keys.find(k => k.sessionKeyAddress.toLowerCase() === sessionKeyAddress.toLowerCase()); + if (!target || isExpired(target)) return null; + saveStoredArray(nodeUrl, walletAddress, keys.map(k => ({ + ...k, + isActive: k.sessionKeyAddress.toLowerCase() === sessionKeyAddress.toLowerCase(), + }))); + return { ...target, isActive: true }; +} + +/** + * Register a fresh session key. `client` must be wallet-backed (no SK signer active) + * so that `signChannelSessionKeyState` produces a 0x00-prefixed wallet signature. + * Pass a temporary wallet-only Client from the caller when an SK is active. + */ +export async function registerSessionKey(args: { + client: Client; + walletAddress: Address; + assets: string[]; + nextVersion: bigint; + expiresAt?: bigint; +}): Promise { + const { client, walletAddress, assets, nextVersion } = args; + + const skPrivateKey = generatePrivateKey(); + const skAccount = privateKeyToAccount(skPrivateKey); + const skMsgSigner = new EthereumMsgSigner(skPrivateKey); + const expiresAtSec = args.expiresAt ?? BigInt(Math.floor(Date.now() / 1000) + EXPIRY_SECONDS); + + const state: ChannelSessionKeyStateV1 = { + user_address: walletAddress, + session_key: skAccount.address, + version: nextVersion.toString(), + assets, + expires_at: expiresAtSec.toString(), + user_sig: '', + session_key_sig: '', + }; + + state.user_sig = await client.signChannelSessionKeyState(state); + state.session_key_sig = await client.signChannelSessionKeyOwnership(state, skMsgSigner); + await client.submitChannelSessionKeyState(state); + + return { + privateKey: skPrivateKey, + sessionKeyAddress: skAccount.address, + walletAddress, + version: state.version, + assets, + expiresAt: state.expires_at, + userSig: state.user_sig as Hex, + }; +} + +/** + * Update an existing session key in-place (same address + private key, incremented version). + * Used for renew and revoke. `client` must be wallet-backed — pass a temporary wallet-only + * Client from the caller when an SK is active. + */ +export async function updateSessionKey(args: { + client: Client; + existingKey: StoredSessionKey; + assets: string[]; + expiresAt: bigint; +}): Promise { + const { client, existingKey, assets, expiresAt } = args; + + const nextVersion = BigInt(existingKey.version) + 1n; + + const state: ChannelSessionKeyStateV1 = { + user_address: existingKey.walletAddress, + session_key: existingKey.sessionKeyAddress, + version: nextVersion.toString(), + assets, + expires_at: expiresAt.toString(), + user_sig: '', + session_key_sig: '', + }; + + const skMsgSigner = new EthereumMsgSigner(existingKey.privateKey); + state.user_sig = await client.signChannelSessionKeyState(state); + state.session_key_sig = await client.signChannelSessionKeyOwnership(state, skMsgSigner); + await client.submitChannelSessionKeyState(state); + + return { + ...existingKey, + version: nextVersion.toString(), + assets, + expiresAt: expiresAt.toString(), + userSig: state.user_sig as Hex, + }; +} + +/** + * Build the StateSigner the SDK Client should use when SK is active. The + * session-key signer prepends the 0x01 type byte and embeds the user + * authorization signature for server-side validation. + */ +export function buildSessionKeyStateSigner(sk: StoredSessionKey): StateSigner { + const metadataHash = getChannelSessionKeyAuthMetadataHashV1( + sk.walletAddress, + BigInt(sk.version), + sk.assets, + BigInt(sk.expiresAt), + ); + return new ChannelSessionKeyStateSigner(sk.privateKey, sk.walletAddress, metadataHash, sk.userSig); +} diff --git a/playground/src/toastError.tsx b/playground/src/toastError.tsx new file mode 100644 index 000000000..2feb1bf4b --- /dev/null +++ b/playground/src/toastError.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; +import { toast } from 'sonner'; +import { X, Copy, Check } from 'lucide-react'; + +const MAX_CHARS = 200; + +// Not exported — callers only see showErrorToast(). +function ErrorToastContent({ id, message }: { id: string | number; message: string }) { + const isLong = message.length > MAX_CHARS; + const display = isLong ? `${message.slice(0, MAX_CHARS)}…` : message; + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(message).catch(() => {}); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + + return ( +
+ + {display} + +
+ + {isLong && ( + + )} +
+
+ ); +} + +export function showErrorToast(message: string) { + toast.custom(id => , { + duration: Infinity, + // toast.custom() sets data-styled="false" so Sonner skips its own width/padding CSS. + // Strip the li's toastOptions styles and restore the correct width. + style: { padding: 0, background: 'transparent', border: 'none', boxShadow: 'none', width: 'var(--width)' }, + }); +} diff --git a/playground/src/utils.ts b/playground/src/utils.ts new file mode 100644 index 000000000..aafa49031 --- /dev/null +++ b/playground/src/utils.ts @@ -0,0 +1,37 @@ +import { isAddress } from 'viem'; + +export const FAUCET_ASSETS = new Set(['yusd']); + +export function formatAddress(address: string): string { + if (!address || address.length < 10) return address; + return `${address.slice(0, 6)}…${address.slice(-4)}`; +} + +export function formatBalance(value: { toString(): string } | null | undefined): string { + if (!value) return '0.00'; + const str = value.toString(); + const parts = str.split('.'); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + if (parts.length === 1) parts.push('00'); + else if (parts[1].length === 1) parts[1] = parts[1] + '0'; + return parts.join('.'); +} + +export function timeAgo(date: Date | string | number | null): string { + if (!date) return 'never'; + const then = typeof date === 'number' ? date : new Date(date).getTime(); + const diff = Math.max(0, Date.now() - then); + if (diff < 2000) return 'just now'; + if (diff < 60000) return `${Math.floor(diff / 1000)}s ago`; + if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`; + if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`; + return new Date(then).toLocaleDateString(); +} + +export function isValidAddress(value: string): boolean { + try { + return isAddress(value, { strict: false }); + } catch { + return false; + } +} diff --git a/playground/src/vite-env.d.ts b/playground/src/vite-env.d.ts new file mode 100644 index 000000000..1b934a4aa --- /dev/null +++ b/playground/src/vite-env.d.ts @@ -0,0 +1,19 @@ +/// + +interface ImportMetaEnv { + readonly VITE_NITRONODE_URL?: string; + readonly VITE_FAUCET_URL?: string; + readonly VITE_FAUCET_ENABLED?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + +interface Window { + __ENV__?: { + NITRONODE_URL?: string; + FAUCET_URL?: string; + FAUCET_ENABLED?: string; + }; +} diff --git a/playground/src/walletSigners.ts b/playground/src/walletSigners.ts new file mode 100644 index 000000000..c1f40bcd1 --- /dev/null +++ b/playground/src/walletSigners.ts @@ -0,0 +1,64 @@ +import type { WalletClient, Address, Hex } from 'viem'; + +export class WalletStateSigner { + constructor(private walletClient: WalletClient) {} + + getAddress(): Address { + if (!this.walletClient.account?.address) { + throw new Error('Wallet client does not have an account address'); + } + return this.walletClient.account.address; + } + + async signMessage(hash: Hex): Promise { + if (!this.walletClient.account) { + throw new Error('Wallet client does not have an account'); + } + return await this.walletClient.signMessage({ + account: this.walletClient.account, + message: { raw: hash }, + }); + } +} + +export class WalletTransactionSigner { + constructor(private walletClient: WalletClient) {} + + getAddress(): Address { + if (!this.walletClient.account?.address) { + throw new Error('Wallet client does not have an account address'); + } + return this.walletClient.account.address; + } + + async sendTransaction(_tx: unknown): Promise { + throw new Error('sendTransaction requires a wallet client - use the blockchain client instead'); + } + + async signMessage(message: { raw: Hex }): Promise { + return await this.signRaw(message.raw); + } + + async signPersonalMessage(hash: Hex): Promise { + if (!this.walletClient.account) { + throw new Error('Wallet client does not have an account'); + } + return await this.walletClient.signMessage({ + account: this.walletClient.account, + message: { raw: hash }, + }); + } + + async signRaw(hash: Hex): Promise { + if (!this.walletClient.account) { + throw new Error('Wallet client does not have an account'); + } + return await this.walletClient.signTypedData({ + account: this.walletClient.account, + domain: { name: 'Nitrolite', version: '1', chainId: 1 }, + types: { Message: [{ name: 'data', type: 'bytes32' }] }, + primaryType: 'Message', + message: { data: hash }, + }); + } +} diff --git a/playground/tailwind.config.js b/playground/tailwind.config.js new file mode 100644 index 000000000..ced92e1b8 --- /dev/null +++ b/playground/tailwind.config.js @@ -0,0 +1,25 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + colors: { + 'bg-base': 'var(--bg-base)', + 'bg-surface': 'var(--bg-surface)', + 'bg-elevated': 'var(--bg-elevated)', + border: 'var(--border)', + 'text-primary': 'var(--text-primary)', + 'text-muted': 'var(--text-muted)', + accent: 'var(--accent)', + 'accent-dim': 'var(--accent-dim)', + error: 'var(--error)', + success: 'var(--success)', + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + mono: ['JetBrains Mono', 'monospace'], + }, + }, + }, + plugins: [], +}; diff --git a/playground/tsconfig.json b/playground/tsconfig.json new file mode 100644 index 000000000..5e1feb437 --- /dev/null +++ b/playground/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/playground/vite.config.ts b/playground/vite.config.ts new file mode 100644 index 000000000..0b8678573 --- /dev/null +++ b/playground/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + // App is deployed behind the same hostname as nitronode, mounted at + // `/v1/playground`. Asset URLs and the SPA entry are rewritten by Vite to + // include the prefix; nginx serves files from the same prefix in-cluster. + base: '/v1/playground/', + plugins: [react()], + server: { + port: 3001, + }, + define: { + global: 'globalThis', + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}); diff --git a/protocol-description.md b/protocol-description.md index e0365a2be..134b8ff26 100644 --- a/protocol-description.md +++ b/protocol-description.md @@ -84,14 +84,35 @@ The off-chain protocol is responsible for: * withdraw, * or migrate the channel. -4. **Flow control** +4. **Node liquidity monitoring** + + * The Node continuously monitors its on-chain vault balance (`_nodeBalances`) across all supported chains. + * When the available vault balance on a given chain approaches or falls below the amount required to back pending off-chain allocations, the Node takes corrective action: + + * enforce states that release locked Node funds back to the vault (e.g. checkpoint or close idle channels), + * rebalance liquidity across chains via cross-chain operations, + * or fire alerts for operator intervention. + * The Node must not co-sign states that would require locking more vault funds than are currently available, as such states would fail on-chain enforcement. + + > **NOTE:** Node liquidity monitoring is not yet enforced in the Nitronode implementation but will be introduced in the near future. + + If Node liquidity monitoring is absent or fails, **no user funds are at risk**. On-chain enforcement always relies on the latest mutually signed state, and the previous on-chain state remains valid and enforceable at all times. The practical consequence is operational: a co-signed state that requires Node vault funds may fail when submitted on-chain if the vault has been depleted in the interim. In such cases, the Node simply replenishes its vault and resubmits. Users retain full access to their funds through challenge and closure paths based on the last successfully enforced state. + +5. **Flow control** * When a cross-chain escrow or migration is in progress: * the Node **stops issuing new states**, * until the process completes or is challenged. -5. **Optimistic bridging** + This is an **off-chain responsibility** enforced by the Node. The on-chain contract cannot enforce + operation ordering because it has no visibility into pending operations on other chains. Instead, + the contract is designed to handle concurrent cross-chain operations correctly — for example, + escrow operations remain reachable even after a subsequent migration on the same chain. Such + concurrent code paths should never be reached under correct Node behavior, but the on-chain + contract handles them safely to guarantee fund recovery in all cases. + +6. **Optimistic bridging** * Cross-chain actions are **not atomically verifiable** on-chain. * Correctness is ensured by: @@ -110,11 +131,12 @@ The off-chain protocol is responsible for: * When a User **sends** funds off-chain: * user allocation decreases, - * node net flow increases. + * node net flow decreases. + * When a User **receives** funds off-chain: * user allocation increases, - * node net flow decreases. + * node net flow increases. These changes are reflected only in cumulative net flows until enforced on-chain. @@ -162,7 +184,7 @@ It does not reconstruct intent — it **verifies and enforces signed states** by While operating: -* Any **newer signed state** may be enforced on-chain. +* Any **newer signed state** may be enforced on-chain by any party, with one exception: `INITIATE_ESCROW_DEPOSIT` on the home chain may only be submitted by the Node (see [Challenge rules](#challenge-rules)). * Enforcement may: * pull funds from User, @@ -189,6 +211,8 @@ Off-chain activity can continue indefinitely between enforcements. * funds are pulled from User, * locked into the channel. +**Native ETH vs ERC20 deposit mechanics:** For ERC20 tokens, the contract pulls funds from the user's address via `safeTransferFrom` using a prior allowance — any party can submit the signed state and the user's approved funds are transferred. For native ETH (`token = address(0)`), the **caller** must attach the required amount as `msg.value`. This means native ETH deposit states must be submitted by the user (or by a party willing to supply the ETH on their behalf). This asymmetry also applies to escrow deposit initiation and escrow withdrawal finalization on the non-home chain. + --- ### 4. Withdrawal (single-chain) @@ -228,11 +252,16 @@ Challenges protect against: ### Challenge rules -* Only channels in `OPERATING` can be challenged. +* Only channels in `OPERATING` or `MIGRATING_IN` can be challenged. * A challenge references a signed state. * If the challenged state is **older than the latest signed state**: * the newest valid signed state **must be enforced first**, regardless of its intent. +* The following intents **cannot** be submitted via `challengeChannel`: + + * `CLOSE` — channel closure is a terminal operation; enforcing it leaves no live channel to dispute. Parties holding a valid CLOSE state should call `closeChannel` directly instead. + * `FINALIZE_MIGRATION` on the **old home chain** (channel status `OPERATING`/`DISPUTED`) — this would release the node's funds and move the channel to `MIGRATED_OUT`, which is incompatible with entering `DISPUTED` state. + * `INITIATE_ESCROW_DEPOSIT` on the **home chain** by a non-Node caller — only the Node may submit this intent on the home chain, whether via `initiateEscrowDeposit` or `challengeChannel`. This prevents a DDoS attack in which an attacker can lock Node liquidity without committing any funds. The user retains full fund-recovery ability: `INITIATE_ESCROW_DEPOSIT` leaves the user's home-chain allocation unchanged, so challenging the home-chain channel with the immediate predecessor state produces an identical fund distribution. Invariant: @@ -261,6 +290,23 @@ Invariant: --- +### Off-chain behaviour during dispute + +While a channel is in `CHALLENGED` (i.e. on-chain `DISPUTED`) status, the Node and the user follow additional off-chain rules: + +* **Node stops signing receive states.** Incoming off-chain transfer credits and app-session release credits targeting the channel are not co-signed. They are appended as unsigned entries to the channel's state history (a per-channel queue). +* **User-initiated operations are blocked.** The user cannot deposit, withdraw, transfer, or lock funds into an app session. Only receiving funds (which the Node queues per the previous rule) is permitted. +* **Operations remain blocked even after the challenge timer expires**, until the channel is explicitly closed and `ChannelClosed` event is seen by the Node. + +Two resolution paths: + +* **Challenge cleared** (a newer mutually signed state is enforced, returning the channel to `OPERATING`): the Node signs only the off-chain head — the highest-version queued "receive" state — so the channel's actual latest state is fully co-signed; the user countersigns and acknowledges. Earlier queued entries remain unsigned in history; the head carries forward their cumulative balance impact, so normal flow resumes with no gap and no credit lost. +* **Challenge expires and the channel is closed**: a single `challenge_rescue` state is committed to the user's next epoch, detached from the closed channel — equivalent to the funds arriving while the user did not have a channel. Its amount is the **net effect on the home-channel balance of transitions stored strictly above the closure version** at the closed channel's epoch: receives (`transfer_receive`, `release`) contribute positively; sends (`transfer_send`, `commit`) contribute negatively; other transition kinds (deposit, withdrawal, escrow, migrate, finalize) are excluded because they require onchain backing the chain did not enforce. Signed (pre-challenge) and unsigned (during-challenge) rows both contribute. The result is clamped at zero so an adversarial close at a version where the user's own balance was higher than the off-chain head cannot dock the user further. When no relevant transitions exist above closure, the state carries zero credit and serves only to advance the state chain off the closed channel so future operations are not wedged on it. + +The purpose of these rules is to ensure that a `CHALLENGED` channel cannot have its dispute cleared as a side effect of incoming third-party transfers, and that the user is made whole for net real value owed by the Node regardless of which resolution path is taken. + +--- + ## Channel closure A channel can be closed: @@ -275,8 +321,9 @@ A channel can be closed: Closure: -* pushes all remaining allocations to User and Node, -* sets channel status to CLOSED. +* Both parties encode their final payouts as negative net flow deltas (equivalent to a full withdrawal), so the CLOSE state must have `userAllocation == 0` and `nodeAllocation == 0`. +* The net flow deltas push all remaining funds to User and Node. +* Sets channel status to CLOSED. --- @@ -366,12 +413,29 @@ This logic mirrors the channel closure mechanism: if a challenge is not substant --- +## Escrow deposit purge queue + +After an escrow deposit is resolved, the node's locked funds must be returned to the node vault. The contract maintains a FIFO queue of escrow deposit IDs (`_escrowDepositIds`), sorted by `unlockAt` ascending, and a monotonically advancing head pointer (`escrowHead`). + +A purge pass processes queue entries in order: + +* **FINALIZED** entries are skipped — funds were already credited during finalization. +* **DISPUTED** entries (challenge still active) are skipped — the challenge outcome is pending. +* **INITIALIZED** entries past their `unlockAt` timestamp are purged — the node's locked amount is returned to the node vault and the entry is marked FINALIZED. +* **INITIALIZED** entries not yet at `unlockAt` stop the scan — because the queue is sorted by `unlockAt` ascending, no subsequent entry can be purgeable either. + +The scan is bounded by a `maxSteps` budget that counts every inspected entry, whether skipped, purged, or halting. `_purgeEscrowDeposits` is called automatically on every protocol operation with a budget of `MAX_DEPOSIT_ESCROW_STEPS = 64`. The public `purgeEscrowDeposits(maxSteps)` function allows any caller to drain a backlog explicitly. + +--- + ## Home chain migration Migration enables moving the channel's "home" security chain from one blockchain to another, preserving allocations and cumulative accounting. Like other cross-chain operations, migration is **two-phase** and **optimistic**. +> **NOTE:** Home chain migration is **not yet active in the Nitronode off-chain implementation**. The on-chain `ChannelHub` contract fully implements both `initiateMigration()` and `finalizeMigration()`, and the migration protocol is fully specified below. The Nitronode off-chain flow — including the `migrate` transition type, two-chain event coordination, and the `MigrationInInitiated` / `MigrationOutFinalized` lifecycle — has not yet been implemented. Submitting a `migrate` transition via the `channels.v1.submit_state` RPC method returns an error. This section documents the intended design for future implementation. + --- ### Preparation phase (INITIATE_MIGRATION) @@ -389,6 +453,7 @@ The preparation phase establishes the channel on the target (non-home) chain: * creates a channel on the non-home chain with status `MIGRATING_IN`, * locks Node's funds on the non-home chain. * Implementation note: States are swapped before storing to maintain the invariant that `homeLedger` represents the current chain. +* A `MIGRATING_IN` channel **is treated as the home chain** for all subsequent on-chain operations: because the swap has already been applied to the stored state, `homeLedger` already describes the current chain and ChannelEngine processes operations (deposit, withdraw, checkpoint, escrow, close) using standard home chain logic. The only distinction from `OPERATING` is that `finalizeMigration()` has not yet been called to confirm the migration on the old home chain. **On the home chain:** @@ -513,9 +578,23 @@ This works because `prevStoredState` was swapped during `INITIATE_MIGRATION`. * Signatures are validated **before** swapping (using the original signed state) * After swapping, signatures are invalidated (`userSig = ""`, `nodeSig = ""`) to prevent misuse * The swapped state is only used internally for storage and validation -* Events emit the original signed state (before swap) for off-chain observability +* Migration lifecycle events (`MigrationInInitiated`, `MigrationOutInitiated`, `MigrationInFinalized`, `MigrationOutFinalized`) emit the original signed state (before swap) for off-chain observability +* `ChannelClosed` is an exception: it emits `meta.lastState`, which on the new home chain is the already-swapped state stored during `initiateMigration()` * This approach maintains the critical invariant: **ChannelEngine always sees homeLedger as the current chain** +#### Dual-close during abandoned migration + +If a migration is initiated but never finalized and both channel copies are subsequently challenged and closed after timeout, `ChannelClosed` can be emitted on both chains for the same `channelId` and state version but with **opposite `homeLedger`/`nonHomeLedger` orientation**: + +* Old home chain (`OPERATING`/`DISPUTED`): emits `finalState` with `homeLedger` = old home chain (original orientation) +* New home chain (`MIGRATING_IN`/`DISPUTED`): emits `finalState` with `homeLedger` = new home chain (swapped orientation) + +On-chain fund accounting is correct in both cases — each chain pays from its own locally stored allocations. The concern is for off-chain consumers: + +* Index and key `ChannelClosed` events by `(chainId, ChannelHub, channelId)`, never by `channelId` alone +* Treat each emission as a distinct local settlement; there is no single canonical `finalState` for an abandoned migration +* Code that persists the emitted `finalState` must handle the swapped ledger orientation for channels in `MIGRATING_IN` status + --- ## Security model summary @@ -530,6 +609,16 @@ This works because `prevStoredState` was swapped during `INITIATE_MIGRATION`. * challenges always resolve by enforcing the newest valid state, * stalled cross-chain operations can always be completed or reverted. +* **Token compatibility**: + + Only standard ERC20 tokens and native ETH are supported. The following token types are incompatible with the static ledger model: + + * **Rebasing tokens** (e.g. stETH, aTokens): their autonomous balance changes are invisible to the ledger and create unrecoverable accounting divergence. Use non-rebasing wrappers instead (e.g. wstETH). + * **Fee-on-transfer tokens**: the amount received by the contract is less than the amount recorded, causing the ledger to overstate holdings from the very first deposit. + * **Tokens without `decimals()`**: the contract calls `IERC20Metadata.decimals()` on every state transition; tokens that do not implement this optional ERC-20 extension are rejected on-chain with `FailedToFetchDecimals`. Unlike rebasing and fee-on-transfer issues (which silently corrupt accounting), missing `decimals()` causes an immediate hard revert. + + There is no hard-coded guardrail preventing deposit of rebasing or fee-on-transfer tokens — the contract will accept them, but any discrepancy will produce undefined accounting behavior for all users of that token. Enforcement is off-chain: the Node will not sign states that reference unsupported token types. + * **Transfer failure resilience**: Outbound transfers (to users) never revert on failure: * Failed transfers (due to blacklists, hooks, or token features) accumulate in a reclaim balance, @@ -540,11 +629,13 @@ This works because `prevStoredState` was swapped during `INITIATE_MIGRATION`. 2. **Node fund lock**: User forces Node to lock large funds via escrow deposit, then blocks all recovery operations with minimal capital. * Combined gas limiting + reclaim pattern ensures channel operations continue regardless of transfer success. +* **Node trust for off-chain transfer routing**: Off-chain transfers between parties are routed through the Node. The sender signs a state where their allocation decreases; the Node is expected to countersign it and also countersign a corresponding credit state for the receiver. The on-chain contract cannot enforce atomicity between two independent channel updates. A malicious Node could apply the sender's state while withholding the receiver's credit, effectively capturing the transferred funds. Users must trust the Node to faithfully execute both legs of every off-chain transfer. + --- ## Signature validation -The protocol supports flexible signature validation through two complementary systems: a per-node validator registry and a bitmask for agreed validators. This design prevents signature forgery attacks while enabling custom signature schemes and maintaining cross-chain compatibility. +The protocol supports flexible signature validation through two complementary systems: a validator registry and a bitmask for agreed validators. This design prevents signature forgery attacks while enabling custom signature schemes and maintaining cross-chain compatibility. ### Validator selection via approved validators bitmask @@ -559,15 +650,16 @@ Since `approvedSignatureValidators` is part of the `channelId` computation, agre * Zero transaction overhead (no separate validator registration needed) * Prevents node-controlled validator forgery attacks * Default ECDSA validator always available as fallback +* **Signature domain**: The default on-chain ECDSA validator accepts both EIP-191 (`eth_sign`) and raw `keccak256` signatures (this is done for extensibility), while the Nitronode off-chain validator accepts EIP-191 only. All client-produced signatures must use EIP-191 to be valid on both paths. ### Node validator registry -The protocol uses a per-node validator registry where nodes register signature validators and assign them 1-byte identifiers (0x01-0xFF). +The protocol uses a validator registry where NODE registers signature validators and assigns them 1-byte identifiers (0x01-0xFF). -**Design rationale:** This allows nodes to use flexible signature schemes (SessionKey, multi-sig, etc.) for their own signatures while preventing them from controlling user signature validation. Benefits: +**Design rationale:** This allows NODE to use flexible signature schemes (SessionKey, multi-sig, etc.) for its own signatures while preventing it from controlling user signature validation. Benefits: -* Nodes can enforce their security requirements for node signatures -* Nodes benefit from flexible validator implementations +* NODE can enforce its security requirements for node signatures +* NODE benefits from flexible validator implementations * Cross-chain compatibility (validator addresses don't affect channelId or signature verification) * User signatures remain protected via approved validators bitmask @@ -619,6 +711,57 @@ The dual validator selection system solves critical cross-chain problems: --- +### Bootstrap problem: validating the initial user signature + +> Full analysis with all considered options and trade-offs: [`contracts/initial-user-sig-validation.md`](contracts/initial-user-sig-validation.md). + +The `approvedSignatureValidators` bitmask protects user signatures on existing channels — the bitmask is part of `channelId`, so it is covered by every previously signed state. However, for `createChannel` there is no prior state: the bitmask comes from calldata supplied by the transaction sender, which may be the node itself. + +This creates a circular dependency: the validator used to confirm the user's consent to the `ChannelDefinition` is selected from data the node controls. A node (basically, any address could be a node) could register a permissive validator and craft a `ChannelDefinition` that points to it, opening a channel without any real user signature. + +The key distinction is between **bootstrap validation** (proving the user consented to the initial `ChannelDefinition`) and **ongoing validation** (proving the user authorized a specific state on an already-agreed channel). The `approvedSignatureValidators` bitmask is the right tool for ongoing validation but cannot self-validate at creation time. + +#### Current deployment model: per-node ChannelHub + +The current implementation binds each ChannelHub to a single node address at deploy time (`NODE` immutable). `_requireValidDefinition` rejects any `createChannel` call where `def.node != NODE`. This eliminates the any-address variant of the attack: a malicious actor cannot forge user signatures on a ChannelHub bound to a different node. + +Within the trust boundary of the bound node the original vulnerability remains. Users who interact with a given deployment must already trust that node — they sign off-chain states with it and grant it ERC20 allowances — so the residual risk sits inside an existing trust relationship rather than being exploitable by an arbitrary third party. + +A contract-enforced `VALIDATOR_ACTIVATION_DELAY` (1 day) provides a partial, targeted defence within this trust boundary, effective only when users actively monitor on-chain registrations within the window: a newly registered validator cannot be used until the delay has elapsed, giving users time to detect a compromise and revoke ERC20 approvals before the attack can execute. Validators are permanent once registered — there is no deactivation mechanism. Users should subscribe to `ValidatorRegistered` events on the ChannelHub contract and avoid granting large standing ERC20 approvals; see `contracts/SECURITY.md` for concrete guidance. + +A consequence of this model is that each node requires its own ChannelHub deployment; a single contract instance cannot serve multiple independent nodes. + +#### Stronger alternatives + +Two designs fully close the bootstrap gap without per-node deployments: + +**Protocol-managed bootstrap registry (Option F).** A separate registry, controlled by a protocol multisig (`bootstrapAdmin`), lists the only validators that may be used for the `createChannel` user signature. Nodes have no influence over this registry. The initial set covers ECDSA and ERC-1271; additional schemes (e.g. an ERC-4337 freezer validator) can be added by governance without redeployment. The remaining trust assumption is that the multisig is not compromised. + +**Two-registry system with tiered trusted validators (Option G).** The trusted validator set is split into a hardcoded tier (validator IDs 0–2, in contract bytecode) and a governance tier (IDs 3+, extensible by a multisig with a contract-enforced activation delay). `createChannel` accepts only hardcoded-tier IDs for the user signature; no governance action can influence it. + +Subsequent operations accept both tiers, filtered by the bitmask stored at creation time. This makes `createChannel` fully admin-proof while preserving extensibility for later operations. ERC-4337 wallets with key-rotation needs are supported within the hardcoded tier via a FreezerProxy ERC-1271 wrapper, requiring no governance action. + +--- + +## Informational Events + +Several `ChannelHub` events are **informational** — emitted on a best-effort basis when a specific dedicated function path is taken, but not guaranteed to fire for every logical occurrence of the operation they name. Because the protocol allows any newer valid signed state to be enforced directly (e.g. a standard deposit or checkpoint on a `MIGRATING_IN` channel), intermediate cross-chain states can be bypassed without calling their dedicated functions, and therefore without emitting their events. + +External consumers such as indexers, SDKs, and analytics tooling **must not treat these events as exhaustive signals**. The canonical terminal events for each cross-chain flow remain reliable and are always emitted. + +The following events are informational: + +* **`MigrationInFinalized`** — not emitted if a `MIGRATING_IN` channel transitions to `OPERATING` via any standard operation (deposit, withdraw, checkpoint, challenge) rather than an explicit `finalizeMigration()` call. The canonical migration completion signal is `MigrationOutFinalized`, which is always emitted unconditionally on the old home chain. +* **`MigrationOutInitiated`** — not emitted if a newer signed state is enforced on the old home channel that supersedes the explicit initiation state. +* **`EscrowDepositFinalized`** — not emitted if the non-home channel advances past escrow finalization via a newer signed state. +* **`EscrowDepositFinalizedOnHome`** — not emitted if the home channel advances past the escrow finalization acknowledgement via a newer signed state. +* **`EscrowWithdrawalInitiatedOnHome`** — not emitted if the home channel advances past the escrow withdrawal initiation acknowledgement via a newer signed state. +* **`EscrowWithdrawalFinalizedOnHome`** — not emitted if the home channel advances past the escrow withdrawal finalization acknowledgement via a newer signed state. + +The Nitronode does not rely on any of these informational events for its state machine. For migration, the Nitronode watches `MigrationInInitiated` (the on-chain signal establishing the new home chain) and `MigrationOutFinalized` (the unconditional completion signal on the old home chain). + +--- + ## Mental model * Off-chain protocol **decides what should happen**. diff --git a/scripts/check-protocol-drift.sh b/scripts/check-protocol-drift.sh new file mode 100755 index 000000000..3ea0148dc --- /dev/null +++ b/scripts/check-protocol-drift.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +usage() { + cat <<'USAGE' +Usage: scripts/check-protocol-drift.sh [--static|--runtime] + + --static Run deterministic protocol/SDK/compat drift checks. + --runtime Run runtime smoke checks against an ephemeral/local Nitronode. + +Runtime smoke starts an isolated local Nitronode with a temporary config by +default. Set NITRONODE_RUNTIME_SMOKE_EXTERNAL=1, NITRONODE_RUNTIME_SMOKE_WS_URL, +and NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY to run the same lightweight compatibility +smoke against an existing node. This is not a load or stress test. +USAGE +} + +run_package() { + local package_path="$1" + local command_name="$2" + local full_path="$ROOT/$package_path" + + if [[ ! -d "$full_path" ]]; then + echo "::error::drift check package path does not exist: $package_path" >&2 + return 1 + fi + + echo + echo "==> $package_path: npm run $command_name" + ( + cd "$full_path" + npm run "$command_name" + ) +} + +mode="${1:---static}" + +case "$mode" in + --static) + echo "==> Running deterministic Nitrolite protocol drift checks" + run_package "sdk/ts" "drift:check" + run_package "sdk/ts-compat" "drift:check" + ;; + --runtime) + echo "==> Running Nitrolite protocol runtime smoke checks" + run_package "sdk/ts" "build:ci" + run_package "sdk/ts-compat" "build:ci" + node "$ROOT/scripts/drift/runtime-smoke.mjs" + ;; + -h|--help) + usage + ;; + *) + usage >&2 + exit 2 + ;; +esac diff --git a/scripts/drift/generate-app-signing-vectors.go b/scripts/drift/generate-app-signing-vectors.go new file mode 100644 index 000000000..00fcbb93b --- /dev/null +++ b/scripts/drift/generate-app-signing-vectors.go @@ -0,0 +1,139 @@ +//go:build ignore + +package main + +import ( + "encoding/json" + "fmt" + "math" + "os" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/shopspring/decimal" +) + +const ( + user = "0x1111111111111111111111111111111111111111" + svc = "0x2222222222222222222222222222222222222222" +) + +type vector struct { + Name string `json:"name"` + Hash string `json:"hash"` +} + +func main() { + definition := app.AppDefinitionV1{ + ApplicationID: "store-v1", + Participants: []app.AppParticipantV1{ + {WalletAddress: user, SignatureWeight: 1}, + {WalletAddress: svc, SignatureWeight: 1}, + }, + Quorum: 2, + Nonce: 123456789, + } + + appSessionID, err := app.GenerateAppSessionIDV1(definition) + must("generate app session id", err) + + vectors := []vector{ + hashCreate("create_session", definition, `{"cart":"demo"}`), + {Name: "app_session_id", Hash: appSessionID}, + hashUpdate("deposit", app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 2, + Allocations: []app.AppAllocationV1{ + {Participant: user, Asset: "YUSD", Amount: decimal.RequireFromString("1.25")}, + {Participant: svc, Asset: "YUSD", Amount: decimal.RequireFromString("0")}, + }, + SessionData: `{"intent":"deposit"}`, + }), + hashUpdate("operate_purchase", app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: 3, + Allocations: []app.AppAllocationV1{ + {Participant: user, Asset: "YUSD", Amount: decimal.RequireFromString("0.35")}, + {Participant: svc, Asset: "YUSD", Amount: decimal.RequireFromString("0.90")}, + }, + SessionData: `{"intent":"purchase","item_id":1,"item_price":"0.90"}`, + }), + hashUpdate("withdraw", app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: 4, + Allocations: []app.AppAllocationV1{ + {Participant: user, Asset: "YUSD", Amount: decimal.RequireFromString("0.10")}, + {Participant: svc, Asset: "YUSD", Amount: decimal.RequireFromString("0.90")}, + }, + SessionData: `{"intent":"withdraw"}`, + }), + hashUpdate("fractional_deposit", app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: 5, + Allocations: []app.AppAllocationV1{ + {Participant: user, Asset: "YUSD", Amount: decimal.RequireFromString("1.23456789")}, + {Participant: svc, Asset: "YUSD", Amount: decimal.RequireFromString("0")}, + }, + SessionData: `{"intent":"deposit","note":"fractional"}`, + }), + hashUpdate("max_uint64_version", app.AppStateUpdateV1{ + AppSessionID: appSessionID, + Intent: app.AppStateUpdateIntentWithdraw, + Version: math.MaxUint64, + Allocations: []app.AppAllocationV1{ + {Participant: user, Asset: "YUSD", Amount: decimal.RequireFromString("0")}, + {Participant: svc, Asset: "YUSD", Amount: decimal.RequireFromString("1.25")}, + }, + SessionData: `{"intent":"withdraw","boundary":"max_uint64_version"}`, + }), + hashCreate("max_uint64_nonce_create_session", app.AppDefinitionV1{ + ApplicationID: definition.ApplicationID, + Participants: definition.Participants, + Quorum: definition.Quorum, + Nonce: math.MaxUint64, + }, `{"cart":"max-nonce"}`), + hashSessionKey("session_key_state", app.AppSessionKeyStateV1{ + UserAddress: user, + SessionKey: svc, + Version: 1, + ApplicationIDs: []string{"0x00000000000000000000000000000000000000000000000000000000000000a1"}, + AppSessionIDs: []string{"0x00000000000000000000000000000000000000000000000000000000000000b1"}, + ExpiresAt: time.Unix(1739812234, 0).UTC(), + UserSig: "0xSig", + }), + } + + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + must("encode vectors", encoder.Encode(vectors)) +} + +func hashCreate(name string, definition app.AppDefinitionV1, sessionData string) vector { + hash, err := app.PackCreateAppSessionRequestV1(definition, sessionData) + must(name, err) + return vector{Name: name, Hash: hexutil.Encode(hash)} +} + +func hashUpdate(name string, update app.AppStateUpdateV1) vector { + hash, err := app.PackAppStateUpdateV1(update) + must(name, err) + return vector{Name: name, Hash: hexutil.Encode(hash)} +} + +func hashSessionKey(name string, state app.AppSessionKeyStateV1) vector { + hash, err := app.PackAppSessionKeyStateV1(state) + must(name, err) + return vector{Name: name, Hash: hexutil.Encode(hash)} +} + +func must(action string, err error) { + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", action, err) + os.Exit(1) + } +} diff --git a/scripts/drift/runtime-smoke.mjs b/scripts/drift/runtime-smoke.mjs new file mode 100644 index 000000000..dbaab94cb --- /dev/null +++ b/scripts/drift/runtime-smoke.mjs @@ -0,0 +1,402 @@ +#!/usr/bin/env node + +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { setTimeout as sleep } from 'node:timers/promises'; + +import { Client, createSigners, withErrorHandler } from '../../sdk/ts/dist/index.js'; +import { NitroliteClient } from '../../sdk/ts-compat/dist/index.js'; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(scriptDir, '../..'); +const sdkRequire = createRequire(path.join(repoRoot, 'sdk/ts/package.json')); +const WebSocketCtor = globalThis.WebSocket ?? sdkRequire('ws'); +const wsURL = process.env.NITRONODE_RUNTIME_SMOKE_WS_URL ?? 'ws://127.0.0.1:7824/ws'; +const readyTimeoutMs = Number(process.env.NITRONODE_RUNTIME_SMOKE_READY_TIMEOUT_MS ?? 15000); +const adversarialMode = process.env.NITRONODE_RUNTIME_SMOKE_ADVERSARIAL ?? ''; +const externalLogDirInput = process.env.NITRONODE_RUNTIME_SMOKE_LOG_DIR ?? ''; +const useExternalNode = process.env.NITRONODE_RUNTIME_SMOKE_EXTERNAL === '1'; +const anvilPrivateKey = + '0x59c6995e998f97a5a0044966f094538f0d0921e301baca6a9ae52cd7834c90b9'; + +class SmokeError extends Error { + constructor(category, message, cause) { + super(`[${category}] ${message}${cause ? `: ${cause.message ?? cause}` : ''}`); + this.name = 'SmokeError'; + this.category = category; + this.cause = cause; + } +} + +function assertSmoke(condition, category, message) { + if (!condition) { + throw new SmokeError(category, message); + } +} + +function resolveRepoChildPath(input, label) { + const resolved = path.resolve(repoRoot, input); + const relative = path.relative(repoRoot, resolved); + if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) { + throw new SmokeError('setup', `${label} must resolve inside the repository`); + } + return resolved; +} + +function privateKeyForMode() { + const configuredPrivateKey = process.env.NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY; + if (useExternalNode) { + if (!configuredPrivateKey) { + throw new SmokeError( + 'setup', + 'NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY is required when NITRONODE_RUNTIME_SMOKE_EXTERNAL=1' + ); + } + return configuredPrivateKey; + } + + // Well-known Anvil/Hardhat test account #2, used only for isolated local smoke. + return configuredPrivateKey ?? anvilPrivateKey; +} + +function childEnv(configDir) { + const env = { + PATH: process.env.PATH, + HOME: process.env.HOME, + TMPDIR: process.env.TMPDIR, + NITRONODE_CONFIG_DIR_PATH: configDir, + }; + + return Object.fromEntries(Object.entries(env).filter(([, value]) => value !== undefined)); +} + +function logStep(message) { + console.log(`[runtime-smoke] ${message}`); +} + +const externalLogDir = externalLogDirInput + ? resolveRepoChildPath(externalLogDirInput, 'NITRONODE_RUNTIME_SMOKE_LOG_DIR') + : ''; +const privateKey = privateKeyForMode(); + +async function withTimeout(label, promise, timeoutMs = 5000) { + const timeout = sleep(timeoutMs).then(() => { + throw new SmokeError('timeout', `${label} timed out after ${timeoutMs}ms`); + }); + return Promise.race([promise, timeout]); +} + +function openWebSocket(url, timeoutMs = 500) { + return new Promise((resolve, reject) => { + const ws = new WebSocketCtor(url); + let settled = false; + + const finish = (err) => { + if (settled) return; + settled = true; + clearTimeout(timer); + try { + ws.close(); + } catch { + // Ignore close errors while probing readiness. + } + if (err) reject(err); + else resolve(); + }; + + const timer = setTimeout(() => finish(new Error('WebSocket connect timeout')), timeoutMs); + ws.onopen = () => finish(); + ws.onerror = () => finish(new Error('WebSocket connection error')); + ws.onclose = () => finish(new Error('WebSocket closed before open')); + }); +} + +async function waitForWebSocket(url, child = null, timeoutMs = 15000) { + const deadline = Date.now() + timeoutMs; + let lastError = null; + + while (Date.now() < deadline) { + if (child && child.exitCode !== null) { + throw new SmokeError( + 'startup', + `Nitronode exited before readiness with code ${child.exitCode}` + ); + } + + try { + await openWebSocket(url); + return; + } catch (err) { + lastError = err; + await sleep(250); + } + } + + throw new SmokeError( + 'connection', + `Nitronode did not accept WebSocket connections at ${url}`, + lastError + ); +} + +async function stopProcess(child) { + if (child.exitCode !== null || child.signalCode !== null) return; + + child.kill('SIGTERM'); + const exited = await Promise.race([ + once(child, 'exit').then(() => true), + sleep(5000).then(() => false), + ]); + if (exited) return; + + child.kill('SIGKILL'); +} + +async function closeClient(client) { + if (!client) return; + + const closed = await Promise.race([ + client.close().then( + () => true, + (err) => { + console.warn(`[runtime-smoke] client.close failed: ${err.message ?? err}`); + return true; + } + ), + sleep(3000).then(() => false), + ]); + + if (!closed) { + console.warn('[runtime-smoke] client.close timed out; continuing cleanup'); + } +} + +async function runCommand(command, args, options, category) { + return new Promise((resolve, reject) => { + let stderr = ''; + const child = spawn(command, args, options); + child.stderr?.on('data', (chunk) => { + stderr += chunk.toString(); + }); + child.on('error', (err) => reject(new SmokeError(category, `${command} failed to start`, err))); + child.on('exit', (code, signal) => { + if (code === 0) { + resolve(); + return; + } + reject( + new SmokeError( + category, + `${command} ${args.join(' ')} exited with ${signal ?? code}${stderr ? `\n${stderr}` : ''}` + ) + ); + }); + }); +} + +async function writeConfig(configDir) { + await writeFile( + path.join(configDir, '.env'), + [ + 'NITRONODE_DATABASE_DRIVER=sqlite', + 'NITRONODE_SIGNER_TYPE=key', + `NITRONODE_SIGNER_KEY=${privateKey}`, + 'NITRONODE_LOG_LEVEL=error', + '', + ].join('\n') + ); + + if (adversarialMode === 'bad-config') { + await writeFile(path.join(configDir, 'blockchains.yaml'), 'blockchains:\n - name: BAD_NAME\n'); + await writeFile(path.join(configDir, 'assets.yaml'), 'assets: []\n'); + return; + } + + await writeFile(path.join(configDir, 'blockchains.yaml'), 'blockchains: []\n'); + await writeFile(path.join(configDir, 'assets.yaml'), 'assets: []\n'); +} + +async function writeFailureLogs(paths, stdout, stderr, summary) { + await writeFile(paths.stdoutPath, stdout); + await writeFile(paths.stderrPath, stderr); + + if (!externalLogDir) return; + + await mkdir(externalLogDir, { recursive: true }); + await writeFile(path.join(externalLogDir, 'summary.txt'), summary); + await writeFile(path.join(externalLogDir, 'nitronode.stdout.log'), stdout); + await writeFile(path.join(externalLogDir, 'nitronode.stderr.log'), stderr); +} + +async function runSmoke() { + const configDir = await mkdtemp(path.join(tmpdir(), 'nitrolite-runtime-smoke-')); + const binaryPath = path.join(configDir, 'nitronode-smoke'); + const stdoutPath = path.join(configDir, 'nitronode.stdout.log'); + const stderrPath = path.join(configDir, 'nitronode.stderr.log'); + let stdout = ''; + let stderr = ''; + let client = null; + let child = null; + let compatLogLines = []; + + const logs = () => [ + `stdout (${stdoutPath}):`, + stdout.trim() || '', + `stderr (${stderrPath}):`, + stderr.trim() || '', + ].join('\n'); + + try { + if (useExternalNode) { + logStep(`using external Nitronode at ${wsURL}`); + } else { + logStep(`writing isolated config in ${configDir}`); + await writeConfig(configDir); + logStep('building temporary Nitronode binary'); + await runCommand('go', ['build', '-o', binaryPath, './nitronode'], { cwd: repoRoot }, 'setup'); + + logStep(`starting Nitronode and waiting for ${wsURL}`); + child = spawn(binaryPath, { + cwd: repoRoot, + env: childEnv(configDir), + stdio: ['ignore', 'pipe', 'pipe'], + }); + + child.stdout.on('data', (chunk) => { + stdout += chunk.toString(); + }); + child.stderr.on('data', (chunk) => { + stderr += chunk.toString(); + }); + } + + await waitForWebSocket(wsURL, child, readyTimeoutMs); + + const { stateSigner, txSigner } = createSigners(privateKey); + const wallet = stateSigner.getAddress(); + logStep(`creating TS SDK client for wallet ${wallet}`); + client = await withTimeout( + 'Client.create', + Client.create(wsURL, stateSigner, txSigner, withErrorHandler(() => {})) + ); + + logStep('calling ping'); + await withTimeout('client.ping', client.ping()); + + logStep('calling getConfig'); + const config = await withTimeout('client.getConfig', client.getConfig()); + assertSmoke(typeof config.nodeAddress === 'string', 'transform', 'node config nodeAddress is not a string'); + assertSmoke(Array.isArray(config.blockchains), 'transform', 'node config blockchains is not an array'); + assertSmoke( + Array.isArray(config.supportedSigValidators), + 'transform', + 'node config supportedSigValidators is not an array' + ); + if (!useExternalNode) { + assertSmoke( + config.nodeAddress.toLowerCase() === wallet.toLowerCase(), + 'transform', + `expected node address ${wallet}, got ${config.nodeAddress}` + ); + assertSmoke(config.blockchains.length === 0, 'transform', 'runtime smoke config should expose no blockchains'); + } + + logStep('calling getAssets'); + const assets = await withTimeout('client.getAssets', client.getAssets()); + assertSmoke(Array.isArray(assets), 'transform', 'assets response is not an array'); + if (!useExternalNode) { + assertSmoke(assets.length === 0, 'transform', 'runtime smoke config should expose no assets'); + } + + logStep('calling getAppSessions'); + const appSessions = await withTimeout( + 'client.getAppSessions', + client.getAppSessions({ wallet }) + ); + assertSmoke(Array.isArray(appSessions.sessions), 'transform', 'app sessions is not an array'); + assertSmoke(appSessions.sessions.length === 0, 'transform', 'expected no app sessions for smoke wallet'); + + logStep('calling getLastChannelKeyStates'); + const channelKeyStates = await withTimeout( + 'client.getLastChannelKeyStates', + client.getLastChannelKeyStates(wallet) + ); + assertSmoke(Array.isArray(channelKeyStates), 'transform', 'channel key states is not an array'); + + logStep('calling getLastAppKeyStates'); + const appSessionKeyStates = await withTimeout( + 'client.getLastAppKeyStates', + client.getLastAppKeyStates(wallet) + ); + assertSmoke(Array.isArray(appSessionKeyStates), 'transform', 'app session key states is not an array'); + + logStep('validating compat getAppSessionsList mapping'); + const compatClient = Object.create(NitroliteClient.prototype); + compatClient.userAddress = wallet; + compatClient.innerClient = client; + compatClient.assetsBySymbol = new Map(); + compatClient._lastAppSessionsListError = null; + compatClient._lastAppSessionsListErrorLogged = null; + + const originalInfo = console.info; + const originalWarn = console.warn; + let compatSessions; + try { + compatLogLines = []; + console.info = (...args) => compatLogLines.push(['info', ...args].join(' ')); + console.warn = (...args) => compatLogLines.push(['warn', ...args].join(' ')); + compatSessions = await withTimeout( + 'compat.getAppSessionsList', + compatClient.getAppSessionsList() + ); + } finally { + console.info = originalInfo; + console.warn = originalWarn; + } + assertSmoke(Array.isArray(compatSessions), 'compat mapping', 'compat sessions is not an array'); + assertSmoke(compatSessions.length === 0, 'compat mapping', 'expected no compat app sessions'); + assertSmoke( + compatClient.getLastAppSessionsListError() === null, + 'compat mapping', + `compat mapping reported ${compatClient.getLastAppSessionsListError()}` + ); + + logStep('runtime smoke passed'); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + const summary = compatLogLines.length > 0 + ? `${message}\n\ncompat logs:\n${compatLogLines.join('\n')}` + : message; + await writeFailureLogs({ stdoutPath, stderrPath }, stdout, stderr, summary); + console.error(message); + if (compatLogLines.length > 0) { + console.error(`compat logs:\n${compatLogLines.join('\n')}`); + } + if (err instanceof SmokeError) { + console.error(logs()); + } + process.exitCode = 1; + } finally { + try { + await closeClient(client); + } finally { + if (child) { + logStep('stopping Nitronode'); + await stopProcess(child); + } + if (process.exitCode) { + console.error(`runtime smoke logs preserved at ${configDir}`); + } else { + await rm(configDir, { recursive: true, force: true }); + } + } + } +} + +await runSmoke(); +process.exit(process.exitCode ?? 0); diff --git a/sdk/.DS_Store b/sdk/.DS_Store deleted file mode 100644 index a30b13b69..000000000 Binary files a/sdk/.DS_Store and /dev/null differ diff --git a/sdk/PROTOCOL_DRIFT_GUARDS.md b/sdk/PROTOCOL_DRIFT_GUARDS.md new file mode 100644 index 000000000..c4f87a13a --- /dev/null +++ b/sdk/PROTOCOL_DRIFT_GUARDS.md @@ -0,0 +1,112 @@ +# Protocol Drift Guards + +This repo has deterministic drift checks for protocol, `@yellow-org/sdk`, and `@yellow-org/sdk-compat` surfaces that demo apps depend on. + +## Commands + +Run all implemented static checks from the repo root: + +```bash +(cd contracts && forge build) +./scripts/check-protocol-drift.sh --static +``` + +`forge build` is required because ABI drift tests compare checked-in SDK ABIs +against generated Foundry artifacts in `contracts/out`. + +Run package checks directly: + +```bash +(cd sdk/ts && npm run drift:check) +(cd sdk/ts-compat && npm run drift:check) +``` + +Run the lightweight runtime smoke from the repo root: + +```bash +./scripts/check-protocol-drift.sh --runtime +``` + +The runtime smoke builds the TS SDK, builds TS compat, builds a temporary local Nitronode binary, starts it with isolated SQLite config, and connects to `ws://127.0.0.1:7824/ws`. It checks `ping`, `getConfig`, `getAssets`, `getAppSessions`, key-state reads, and compat mapping over the live SDK app-session result. + +This is not a load test. It uses empty local `blockchains` and `assets` config so PR CI does not depend on external RPC endpoints, wallets, or shared Nitronode deployments. + +To run the same lightweight compatibility smoke against an existing Nitronode, use external-node mode: + +```bash +NITRONODE_RUNTIME_SMOKE_EXTERNAL=1 \ +NITRONODE_RUNTIME_SMOKE_WS_URL= \ +NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY=<0x-private-key> \ +./scripts/check-protocol-drift.sh --runtime +``` + +External-node mode does not start a local Nitronode and does not assert local-only empty config. It still checks `ping`, `getConfig`, `getAssets`, `getAppSessions`, key-state reads, and compat mapping. + +## Guard Layers + +- RPC method drift: compares Go RPC method literals, Nitronode router registrations, TS method constants, and public TS client wrappers. +- RPC DTO drift: compares Go JSON-tagged DTO structs against TS request/response interfaces for required fields, optional fields, and scalar/container shape. +- Public API drift: snapshots root runtime exports and compiler-derived TypeScript signatures for `@yellow-org/sdk` and `@yellow-org/sdk-compat`, including type-only exports, interfaces, functions, classes, public class methods, enums, constants, and type aliases. +- ABI drift: compares checked-in `ChannelHub` functions against the current Foundry artifact, checks SDK-consumed ERC20 functions against the ERC20 artifact, and guards the manually checked-in AppRegistry ABI against an explicit consumed-function manifest until that contract artifact exists in this repo. +- Signing drift: compares TS app-session and session-key packers against Go-generated canonical vectors for create, deposit, withdraw, operate, fractional decimal, and uint64 boundary cases. +- Transform drift: checks raw Nitronode response fixtures for app sessions, node config, assets, and strict failure on unsupported required shapes. +- Compat drift: checks current v1 app-session shape, legacy flat fallback shape, and asset decimal conversion in `NitroliteClient.getAppSessionsList()`. +- Runtime smoke drift: starts an isolated local Nitronode and verifies live SDK/compat calls against the current runtime response shape. + +## Intentional Updates + +For intentional public runtime API changes, update snapshots with: + +```bash +cd sdk/ts && npm run drift:check -- -u +cd sdk/ts-compat && npm run drift:check -- -u +``` + +For intentional ABI changes, regenerate artifacts and SDK ABI files before running drift checks: + +```bash +cd contracts && forge build +cd ../sdk/ts && npm run codegen-abi +``` + +For a new RPC method, update all applicable surfaces in the same PR: Go method constants, router registration, TS method constants, and the public TS client wrapper unless the method is intentionally raw-only. + +For a new DTO field, update the Go JSON-tagged struct and TS request/response interface together. Optionality must match unless a small, named override is added to the drift test. + +For a new response transform, add a raw fixture and expected behavior test in the relevant drift test. Unsupported wire shapes should fail clearly instead of silently producing partial data. If the high-level client method performs the transform inline, add a client-level mock test in addition to any isolated transform test. + +For intentional app/session-key signing vector changes, regenerate the Go source-of-truth hashes from the repo root: + +```bash +go run ./scripts/drift/generate-app-signing-vectors.go +``` + +Then update `sdk/ts/test/unit/app-signing-drift.test.ts` with the changed hashes in the same PR as the Go packing/protocol change. + +## Adversarial Proof + +Each guard includes at least one negative test or mutation-style check that proves the guard would fail if the relevant surface drifted. These checks must use fixtures, temp copies, or local in-test mutations. They must not leave tracked files dirty. + +## Troubleshooting + +- Missing RPC method or client wrapper: update Go method constants, router registrations, TS method constants, and the public TS client wrapper together. If the method is intentionally raw-only, add an explicit exemption in the RPC drift test. +- DTO optionality or field drift: compare the failing method/type/field path in the drift output, then update the Go JSON-tagged struct and TS request/response interface in the same PR. +- Public API snapshot drift: treat the diff as an SDK API change. If intentional, update snapshots with `npm run drift:check -- -u` in the affected package and document the API change in the PR body. +- ABI drift: regenerate Foundry artifacts and SDK ABI files with `cd contracts && forge build` and `cd ../sdk/ts && npm run codegen-abi`. If AppRegistry changes, remember it is currently manifest-guarded because the matching artifact is not in this repo. +- Signing hash mismatch: regenerate Go source-of-truth vectors with `go run ./scripts/drift/generate-app-signing-vectors.go`, then inspect whether the change is field order, enum value, amount formatting, nonce/version encoding, or exact session-data bytes. +- Transform fixture failure: update or add raw Nitronode fixtures only for wire shapes the SDK intentionally supports. Do not silently accept missing required fields that would later crash consumers. +- Compat mapping failure: current v1 SDK shapes are primary. Legacy fallbacks must stay explicit in tests; do not add broad best-effort mappers without fixture coverage. +- Runtime setup/startup failure: inspect `runtime-smoke-logs` in CI or the preserved temp log directory locally. `[setup]` points to build/setup, `[startup]` to local Nitronode process exit, `[connection]` to WebSocket readiness, and `[transform]` or `[compat mapping]` to SDK response handling. +- External smoke failure: rerun the manual workflow or local external-node command to confirm it is not shared-environment state. External smoke is release/demo signal, not a PR blocker. + +## CI Policy + +`Test (Protocol Drift Static)` runs on PRs and main pushes. It is deterministic and does not call shared Nitronode deployments. + +`Test (Protocol Drift Runtime)` also runs on PRs and main pushes. It starts an isolated local Nitronode inside the GitHub Actions job and does not use shared external or sandbox endpoints. + +If runtime smoke fails in CI, inspect the `protocol-drift-runtime-smoke-logs` artifact. The smoke command categorizes failures as setup, startup, connection, timeout, transform, or compat mapping failures. + +The runtime job uses read-only repository permissions and no secrets. It builds Nitronode locally instead of pulling or publishing an image, so ordinary PRs do not need package-write permissions. If organization policy restricts forked PR workflows, a maintainer can rerun the same command locally or through an allowed CI rerun. + +External Nitronode checks are manual only through the `Protocol Drift External Smoke` workflow. The workflow requires the caller to provide the WebSocket URL and the repository secret `NITRONODE_RUNTIME_SMOKE_PRIVATE_KEY`, is not PR-blocking, and is not scheduled by default. Team-owned temporary environments can still be useful for release confidence, but they must not become default PR blockers unless their availability and data contract are owned. diff --git a/sdk/THE_WAY_AHEAD.md b/sdk/THE_WAY_AHEAD.md new file mode 100644 index 000000000..713700992 --- /dev/null +++ b/sdk/THE_WAY_AHEAD.md @@ -0,0 +1,311 @@ +# The Way Ahead: Productionalising Nitrolite AI Tooling + +This document is the roadmap for taking our MCP servers from "works if you clone the repo" to "any developer, anywhere, gets Nitrolite AI tooling with one command." It's written for someone who has never published an npm package or a Go binary before. + +--- + +## Where We Are + +| What we have | Status | +|---|---| +| Unified MCP server (`sdk/mcp/`) | Works locally, 30 resources, 8 tools, 3 prompts — covers TypeScript and Go SDKs | +| Protocol docs, nitronode API, SDK source | Indexed at startup by the MCP server | + +**The problem:** The server reads files from the repo at runtime (`docs/protocol/`, `docs/api.yaml`, `sdk/ts/src/`, `sdk/go/`). If you don't have the full repo cloned, it doesn't work. No external developer can use it. + +--- + +## IMPORTANT: Release Parity + +**The MCP server currently reads from repo source on disk, not from the published SDK release.** This means: + +- The MCP parses `sdk/ts/src/client.ts` directly — which may contain unreleased methods not yet in `@yellow-org/sdk` on npm +- The MCP parses `sdk/go/*.go` — same issue, may expose unreleased Go API surface +- Protocol docs in `docs/protocol/` may document unreleased behavior + +**This is fine for internal development** (you want AI to know about in-progress code). But for the published MCP package, this creates a real problem: an AI agent could tell an external developer "use `client.someNewMethod()`" — but when they `npm install @yellow-org/sdk`, that method doesn't exist yet. + +**When publishing (Phase 1 below), you MUST:** + +1. **Embed content from the tagged release commit, not from main.** The CI workflow that publishes the MCP package should check out the corresponding SDK release tag (e.g., `sdk/ts/v1.2.0`) and embed content from that snapshot. +2. **Version-lock the MCP to its SDK.** The MCP package version should track the SDK version it documents. If `@yellow-org/sdk` is at `1.2.0`, the MCP should be `1.2.0` too (or `1.2.0-mcp.1` if iterating on MCP-only changes). +3. **Add a version check at startup.** When running locally from repo source (not published), the MCP should log a warning: "Reading from source — API surface may differ from published SDK." +4. **Coordinate release timing.** Publish the MCP package as part of the same release workflow that publishes the SDK, so they're always in sync. + +This is the single most important detail to get right before publishing. An MCP that tells developers about methods they can't use is worse than no MCP at all. + +--- + +## Phase 1: Publish TS MCP to npm + +**Goal:** Any developer runs `npx @yellow-org/sdk-mcp` and gets the full Nitrolite MCP server. + +### Step 1: Embed content at build time + +The server currently reads files from disk. For npm, we need to bundle all content into the compiled JavaScript. Two approaches: + +**Option A (simpler):** A build script that reads all protocol docs, examples, and SDK source, then generates a `src/embedded-content.ts` file with all content as exported string constants. The server imports from this file instead of using `fs.readFileSync`. + +**Option B:** Use a bundler like `tsup` or `esbuild` with a plugin that inlines file reads at build time. + +Option A is recommended — it's explicit, debuggable, and doesn't add build tool complexity. + +### Step 2: Set up package.json for publishing + +Current `package.json` needs these changes: + +```jsonc +{ + "name": "@yellow-org/sdk-mcp", + "version": "0.1.0", + "description": "MCP server exposing the Nitrolite SDK to AI agents and IDEs", + "main": "dist/index.js", // compiled output, not .ts source + "types": "dist/index.d.ts", + "bin": { + "nitrolite-sdk-mcp": "dist/index.js" + }, + "files": [ // what goes in the npm tarball + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" // scoped packages default to private! + }, + "repository": { + "type": "git", + "url": "https://github.com/layer-3/nitrolite.git", + "directory": "sdk/mcp" + }, + "license": "MIT", + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build" // auto-builds before publish + } +} +``` + +**Important:** Keep the shebang (`#!/usr/bin/env node`) at the top of `sdk/mcp/src/index.ts`. TypeScript preserves shebangs when emitting JavaScript, so `tsc` will carry it through to `dist/index.js` and the `bin` entry will remain executable. `tsup` is still a reasonable alternative build tool here, but it is optional rather than required for shebang handling. + +### Step 3: Create an npm account and get access + +1. **Create account:** Go to https://www.npmjs.com/signup +2. **Enable 2FA:** Avatar > Account Settings > Two-Factor Authentication. Scan QR code with an authenticator app. This is mandatory for publishing. +3. **Get org access:** The `@yellow-org` org already exists (it publishes `@yellow-org/sdk`). An existing org admin must invite you at https://www.npmjs.com/settings/yellow-org/members with the "developer" role (can publish packages). + +### Step 4: Publish manually (first time) + +```bash +# Login to npm (opens browser for auth) +npm login + +# Verify you're logged in +npm whoami + +# Go to the package directory +cd sdk/mcp + +# Build +npm run build + +# Preview what will be in the tarball (sanity check) +npm pack --dry-run + +# Publish! --access public is critical for scoped packages +npm publish --access public +``` + +After this, anyone in the world can run: +```bash +npx @yellow-org/sdk-mcp +``` + +### Step 5: Automate publishing with GitHub Actions + +After the first manual publish, set up automation so future versions publish on git tag. + +**Option A: Trusted Publishing (recommended, no secrets needed)** + +1. On npmjs.com, go to the package > Access > Trusted Publishers > Add GitHub Actions +2. Enter: org=`layer-3`, repo=`nitrolite`, workflow=`publish-sdk-mcp.yml` +3. Create the workflow: + +```yaml +# .github/workflows/publish-sdk-mcp.yml +name: Publish @yellow-org/sdk-mcp + +on: + push: + tags: + - 'sdk-mcp/v*' # triggers on tags like sdk-mcp/v0.2.0 + +permissions: + contents: read + id-token: write # required for OIDC auth with npm + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + - run: npm ci + working-directory: ./sdk/mcp + - run: npm run build + working-directory: ./sdk/mcp + - run: npm publish --access public --provenance + working-directory: ./sdk/mcp + # No NODE_AUTH_TOKEN needed! OIDC handles it. +``` + +4. To publish a new version: +```bash +# Bump version in package.json +cd sdk/mcp +npm version patch # or minor, or major + +# Tag and push +git add sdk/mcp/package.json +git commit -m "chore(sdk-mcp): bump to v0.2.0" +git tag sdk-mcp/v0.2.0 +git push origin main +git push origin sdk-mcp/v0.2.0 +# GitHub Actions takes over from here +``` + +**Option B: npm token (fallback)** + +If trusted publishing doesn't work for your setup: +1. Create a granular access token at npmjs.com > Access Tokens +2. Scope it to `@yellow-org/sdk-mcp` only, with "Require 2FA" disabled (so CI can use it) +3. Add it as `NPM_TOKEN` in GitHub repo secrets +4. Add to the publish step: `env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` + +### Step 6: Submit to the official MCP Registry + +The MCP Registry (registry.modelcontextprotocol.io) is a discovery directory. Getting listed means AI tools can find your server automatically. + +```bash +# Install the publisher CLI +brew install modelcontextprotocol/tap/mcp-publisher + +# Generate server.json in sdk/mcp/ +cd sdk/mcp +mcp-publisher init +``` + +Edit `server.json`: +```json +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.layer-3/nitrolite-sdk-mcp", + "description": "MCP server exposing the Nitrolite state channel SDK to AI agents", + "version": "0.1.0", + "repository": { + "url": "https://github.com/layer-3/nitrolite", + "source": "github", + "id": "layer-3/nitrolite", + "subfolder": "sdk/mcp" + }, + "packages": [{ + "registry_type": "npm", + "name": "@yellow-org/sdk-mcp", + "version": "0.1.0", + "runtime": "node", + "package_arguments": [], + "environment_variables": [] + }] +} +``` + +```bash +# Authenticate with GitHub +mcp-publisher login github # opens browser + +# Publish to registry +mcp-publisher publish +``` + +Verify: `curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=nitrolite"` + +**When updating:** Bump both `version` and `packages[].version` in `server.json`, then run `mcp-publisher publish` again. + +### Common npm gotchas + +- Scoped packages (`@yellow-org/*`) default to **private**. Always use `--access public` on first publish or set `publishConfig.access` in package.json +- `npm pack --dry-run` shows exactly what goes in the tarball — use it before every publish +- You **cannot unpublish** a version after 72 hours. Version numbers are permanent. +- npm requires 2FA for all publishing since late 2025 + +--- + +Go SDK context is now served by the unified TypeScript MCP server (`sdk/mcp`). A separate Go binary distribution is no longer planned — the single npm-published `@yellow-org/sdk-mcp` package covers both TypeScript and Go SDK surfaces. + +--- + +## Phase 2: Cross-IDE Context Files + +These are simple text files that make the repo AI-friendly for every coding tool — not just Claude Code. + +| File | Tool | What it does | +|------|------|-------------| +| `llms.txt` | Any LLM / web crawler | Machine-readable summary of the project | +| `llms-full.txt` | Claude Projects, ChatGPT | Full protocol + SDK reference in one file | +| `.cursorrules` | Cursor | Project-specific AI coding rules | +| `.github/copilot-instructions.md` | GitHub Copilot | Org-wide coding conventions (4K char limit) | +| `.windsurfrules` | Windsurf | Same as cursorrules | + +These are low-effort, high-reach. Content already exists in `.claude/rules/` and `CLAUDE.md` — it just needs to be reformatted. + +--- + +## Phase 3: Agent Skills (SKILL.md) + +The SKILL.md format (from Anthropic, adopted by OpenAI Codex and VS Code Copilot) teaches AI agents *how to do things* — complementary to MCP which provides *tools*. + +Create a `skills/` directory or separate repo (`yellow-org/nitrolite-skills`) with: + +| Skill | What it teaches | +|-------|----------------| +| `build-transfer-app` | How to build a token transfer app from scratch | +| `build-app-session` | How to build a multi-party app session | +| `migrate-sdk` | How to migrate from SDK v0.5.3 to v1.x | +| `build-ai-agent` | How to build an autonomous payment agent | + +Published via: `npx skills add yellow-org/nitrolite-skills` + +--- + +## Phase 4: Hosted Remote MCP (Future) + +Deploy the MCP server as a hosted HTTP endpoint so developers don't need to install anything at all: + +```json +{ + "mcpServers": { + "nitrolite": { + "url": "https://mcp.yellow.org/ts" + } + } +} +``` + +Uses MCP Streamable HTTP transport. Requires infrastructure (Cloudflare Workers or similar) and API key management. Only Thirdweb does this in Web3 today. + +--- + +## Summary: Developer Experience by Phase + +| Phase | Developer experience | Effort | +|-------|---------------------|--------| +| Today | Clone repo, run from source | Already done | +| Phase 1 (npm publish) | `npx @yellow-org/sdk-mcp` — covers both TS and Go SDKs | Medium | +| Phase 2 (context files) | IDE auto-loads project rules | Low | +| Phase 3 (skills) | `npx skills add yellow-org/nitrolite-skills` | Medium | +| Phase 4 (hosted) | Add a URL to IDE config | High | + +**Priority order:** Phase 1 > Phase 2 > Phase 3 > Phase 4 + +Phase 1 (npm publish) is the single highest-impact step. It's the difference between "only our team can use this" and "any developer on the internet can use this with one command." diff --git a/sdk/go/CLAUDE.md b/sdk/go/CLAUDE.md new file mode 100644 index 000000000..b8892665d --- /dev/null +++ b/sdk/go/CLAUDE.md @@ -0,0 +1,42 @@ +# Go SDK + +Official Go SDK for building backend integrations and CLI tools on Nitrolite state channels. + +## Quick Reference + +```bash +# All commands run from the REPO ROOT (not sdk/go/) +go test ./sdk/go/... -v # Test SDK only +go build ./sdk/go/... # Build SDK only +go vet ./sdk/go/... # Lint SDK only + +go test ./... # Test EVERYTHING (nitronode, pkg, sdk, cerebro) +``` + +**Important:** This is NOT a separate Go module. It shares the root `go.mod` (`github.com/layer-3/nitrolite`, Go 1.25). + +## Source Layout + +| File | Purpose | +|------|---------| +| `client.go` | Main SDK client — entry point for all operations | +| `config.go` | Functional options pattern for client configuration | +| `channel.go` | Channel operations (open, close, resize, challenge) | +| `utils.go` | Encoding/packing helpers | +| `doc.go` | Package-level documentation with usage examples | +| `*_test.go` | Tests (same directory, standard Go convention) | +| `mock_dialer_test.go` | Test doubles for WebSocket dialer | + +## Architecture + +- Uses **functional options pattern** for configuration (see `config.go`) +- Key dependency: `github.com/ethereum/go-ethereum` for Ethereum interactions +- Shared packages in `pkg/` (sign, core, rpc, app, blockchain, log) are used by both this SDK and `nitronode/` +- WebSocket-based communication with nitronode via JSON-RPC + +## Conventions + +- Standard Go: `gofmt`, exported names have doc comments +- Error handling: always check and return errors, never ignore +- Tests use standard `testing` package with `*_test.go` in same directory +- Check `pkg/` for shared utilities before creating new ones diff --git a/sdk/go/README.md b/sdk/go/README.md index c20acc395..e952e82d4 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -1,8 +1,10 @@ [![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 +# Nitronode Go SDK -Go SDK for Clearnode payment channels providing both high-level and low-level operations in a unified client: +> Renamed from "Clearnode" in v1.3.0. See [`MIGRATION-NITRONODE.md`](../../MIGRATION-NITRONODE.md) for image / env-var / DNS changes. + +Go SDK for Nitronode 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 - **Blockchain Settlement**: `Checkpoint` - the single entry point for all on-chain transactions - **Low-Level Operations**: Direct RPC access for custom flows and advanced use cases @@ -66,16 +68,18 @@ client.RebalanceAppSessions(ctx, signedUpdates) // Atomic rebalanc ### Session Keys — App Sessions ```go -client.SignSessionKeyState(state) // Sign an app session key state -client.SubmitAppSessionKeyState(ctx, state) // Register/update app session key -client.GetLastAppKeyStates(ctx, userAddress, opts) // Get active app session key states +client.SignSessionKeyState(state) // Wallet UserSig over app session key state +sdk.SignAppSessionKeyOwnership(state, sessionKeySigner) // Session-key holder's SessionKeySig +client.SubmitAppSessionKeyState(ctx, state) // Register/update app session key (both sigs required) +client.GetLastAppKeyStates(ctx, userAddress, opts) // Get app session key states (active-only by default; opts.IncludeInactive=true to include expired) ``` ### Session Keys — Channels ```go -client.SignChannelSessionKeyState(state) // Sign a channel session key state -client.SubmitChannelSessionKeyState(ctx, state) // Register/update channel session key -client.GetLastChannelKeyStates(ctx, userAddress, opts) // Get active channel session key states +client.SignChannelSessionKeyState(state) // Wallet UserSig over channel session key state +sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner) // Session-key holder's SessionKeySig +client.SubmitChannelSessionKeyState(ctx, state) // Register/update channel session key (both sigs required) +client.GetLastChannelKeyStates(ctx, userAddress, opts) // Get channel session key states (active-only by default; opts.IncludeInactive=true to include expired) ``` ### Shared Utilities @@ -110,7 +114,7 @@ func main() { // Create unified client client, _ := sdk.NewClient( - "wss://clearnode-sandbox.yellow.org/v1/ws", + "wss://nitronode-sandbox.yellow.org/v1/ws", stateSigner, txSigner, sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"), @@ -416,8 +420,15 @@ sig, _ := appSessionSigner.Sign(packedRequest) ### Session Keys — App Sessions +Registration requires two signatures: the wallet's `UserSig` (authorizing the delegation) +and the session-key holder's `SessionKeySig` (proving possession of the key being +registered). The node rejects submits that lack a valid `SessionKeySig`. + ```go -// Sign and submit an app session key state +// sessionKeySigner must be a *sign.EthereumMsgSigner (raw EIP-191 signer) +// whose address equals state.SessionKey — not a wrapped sign.Signer, because +// the node recovers SessionKeySig as a raw 65-byte Ethereum message signature. +sessionKeySigner, _ := sign.NewEthereumMsgSigner(sessionKeyPrivHex) state := app.AppSessionKeyStateV1{ UserAddress: client.GetUserAddress(), SessionKey: "0xSessionKey...", @@ -426,21 +437,31 @@ state := app.AppSessionKeyStateV1{ AppSessionIDs: []string{}, ExpiresAt: time.Now().Add(24 * time.Hour), } -sig, err := client.SignSessionKeyState(state) -state.UserSig = sig -err = client.SubmitAppSessionKeyState(ctx, state) +state.UserSig, _ = client.SignSessionKeyState(state) +state.SessionKeySig, _ = sdk.SignAppSessionKeyOwnership(state, sessionKeySigner) +err := client.SubmitAppSessionKeyState(ctx, state) -// Query active app session key states +// Query app session key states (active-only by default) states, err := client.GetLastAppKeyStates(ctx, userAddress, nil) states, err := client.GetLastAppKeyStates(ctx, userAddress, &sdk.GetLastKeyStatesOptions{ SessionKey: &sessionKeyAddr, }) + +// Include expired/revoked latest states (e.g. for rotation flows that need the prior version) +includeInactive := true +states, err = client.GetLastAppKeyStates(ctx, userAddress, &sdk.GetLastKeyStatesOptions{ + SessionKey: &sessionKeyAddr, + IncludeInactive: &includeInactive, +}) ``` ### Session Keys — Channels ```go -// Sign and submit a channel session key state +// sessionKeySigner must be a *sign.EthereumMsgSigner (raw EIP-191 signer) +// whose address equals state.SessionKey — not a wrapped sign.Signer, because +// the node recovers SessionKeySig as a raw 65-byte Ethereum message signature. +sessionKeySigner, _ := sign.NewEthereumMsgSigner(sessionKeyPrivHex) state := core.ChannelSessionKeyStateV1{ UserAddress: client.GetUserAddress(), SessionKey: "0xSessionKey...", @@ -448,15 +469,22 @@ state := core.ChannelSessionKeyStateV1{ Assets: []string{"usdc", "weth"}, ExpiresAt: time.Now().Add(24 * time.Hour), } -sig, err := client.SignChannelSessionKeyState(state) -state.UserSig = sig -err = client.SubmitChannelSessionKeyState(ctx, state) +state.UserSig, _ = client.SignChannelSessionKeyState(state) +state.SessionKeySig, _ = sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner) +err := client.SubmitChannelSessionKeyState(ctx, state) -// Query active channel session key states +// Query channel session key states (active-only by default) states, err := client.GetLastChannelKeyStates(ctx, userAddress, nil) states, err := client.GetLastChannelKeyStates(ctx, userAddress, &sdk.GetLastChannelKeyStatesOptions{ SessionKey: &sessionKeyAddr, }) + +// Include expired/revoked latest states (e.g. for rotation flows that need the prior version) +includeInactive := true +states, err = client.GetLastChannelKeyStates(ctx, userAddress, &sdk.GetLastChannelKeyStatesOptions{ + SessionKey: &sessionKeyAddr, + IncludeInactive: &includeInactive, +}) ``` ## Key Concepts @@ -713,7 +741,7 @@ For understanding how operations work under the hood: ## Requirements - Go 1.21+ -- Running Clearnode instance +- Running Nitronode instance - Blockchain RPC endpoint (for Checkpoint settlement) ## License diff --git a/sdk/go/app_session.go b/sdk/go/app_session.go index e3627cb49..b88d3e82a 100644 --- a/sdk/go/app_session.go +++ b/sdk/go/app_session.go @@ -8,6 +8,7 @@ import ( "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" ) @@ -69,17 +70,22 @@ func (c *Client) GetAppSessions(ctx context.Context, opts *GetAppSessionsOptions // GetAppDefinition retrieves the definition for a specific app session. // +// Returns (nil, nil) when no app session exists for the given ID — absence is +// a successful response, not an error. +// // Parameters: // - appSessionID: The application session ID // // Returns: -// - app.AppDefinitionV1 with participants, quorum, and application info +// - app.AppDefinitionV1 with participants, quorum, and application info, or +// nil if absent // - Error if the request fails // // Example: // // def, err := client.GetAppDefinition(ctx, "session123") -// fmt.Printf("App: %s, Quorum: %d\n", def.Application, def.Quorum) +// if err != nil { return err } +// if def == nil { /* not found */ } func (c *Client) GetAppDefinition(ctx context.Context, appSessionID string) (*app.AppDefinitionV1, error) { if appSessionID == "" { return nil, fmt.Errorf("app session ID required") @@ -92,7 +98,11 @@ func (c *Client) GetAppDefinition(ctx context.Context, appSessionID string) (*ap return nil, fmt.Errorf("failed to get app definition: %w", err) } - def, err := transformAppDefinition(resp.Definition) + if resp.Definition == nil { + return nil, nil + } + + def, err := transformAppDefinition(*resp.Definition) if err != nil { return nil, fmt.Errorf("failed to transform app definition: %w", err) } @@ -175,6 +185,10 @@ func (c *Client) SubmitAppSessionDeposit(ctx context.Context, appStateUpdate app return "", fmt.Errorf("failed to get latest state: %w", err) } + if currentState == nil { + return "", fmt.Errorf("no channel state to advance for AppSession") + } + nextState := currentState.NextState() _, err = nextState.ApplyCommitTransition(appUpdate.AppSessionID, depositAmount) @@ -182,7 +196,7 @@ func (c *Client) SubmitAppSessionDeposit(ctx context.Context, appStateUpdate app return "", fmt.Errorf("failed to apply commit transition: %w", err) } - stateSig, err := c.SignState(nextState) + stateSig, err := c.ValidateAndSignState(currentState, nextState) if err != nil { return "", fmt.Errorf("failed to sign state: %w", err) } @@ -276,8 +290,20 @@ func (c *Client) RebalanceAppSessions(ctx context.Context, signedUpdates []app.S // Session Key Methods // ============================================================================ -// SubmitAppSessionKeyState submits a session key state for registration or update. -// The state must be signed by the user's wallet to authorize the session key delegation. +// SubmitAppSessionKeyState submits a session key state for registration, update, +// revocation, or re-activation. The state must carry both the wallet's UserSig +// (authorizing the delegation) and the session-key holder's SessionKeySig (proving +// possession of the key); submits without a valid SessionKeySig are rejected on every +// path, including revocation — the session key must co-sign its own deactivation. +// Wallet-only revocation (for a lost or compromised key) is not supported by this +// method. +// +// Set state.ExpiresAt to a future time to register or update the key. Set it to a +// value at or before time.Now() to revoke the key — the auth path filters by +// expires_at, so the key is deactivated immediately while keeping the same monotonic +// version sequence. A later submit with the next version and a future ExpiresAt +// re-activates the same session key address. Negative unix timestamps are rejected +// by the server. // // Parameters: // - state: The session key state containing delegation information @@ -294,8 +320,9 @@ func (c *Client) RebalanceAppSessions(ctx context.Context, signedUpdates []app.S // ApplicationIDs: []string{"app1"}, // AppSessionIDs: []string{}, // ExpiresAt: time.Now().Add(24 * time.Hour), -// UserSig: "0x...", // } +// state.UserSig, _ = client.SignSessionKeyState(state) +// state.SessionKeySig, _ = sdk.SignAppSessionKeyOwnership(state, sessionKeySigner) // err := client.SubmitAppSessionKeyState(ctx, state) func (c *Client) SubmitAppSessionKeyState(ctx context.Context, state app.AppSessionKeyStateV1) error { req := rpc.AppSessionsV1SubmitSessionKeyStateRequest{ @@ -312,16 +339,21 @@ func (c *Client) SubmitAppSessionKeyState(ctx context.Context, state app.AppSess type GetLastKeyStatesOptions struct { // SessionKey filters by a specific session key address SessionKey *string + // IncludeInactive, when set to true, includes latest states whose expires_at is in + // the past (expired or revoked). Defaults to false (active-only) when nil or false. + IncludeInactive *bool } -// GetLastAppKeyStates retrieves the latest session key states for a user. +// GetLastAppKeyStates retrieves the latest app session key states for a user. +// By default only currently active (non-expired) states are returned; set +// opts.IncludeInactive to true to include expired or revoked latest states. // // Parameters: // - userAddress: The user's wallet address -// - opts: Optional filters (pass nil for no filters) +// - opts: Optional filters (pass nil for active-only with no session-key filter) // // Returns: -// - Slice of AppSessionKeyStateV1 with the latest non-expired session key states +// - Slice of AppSessionKeyStateV1 with the latest session key states matching the filter // - Error if the request fails // // Example: @@ -336,6 +368,7 @@ func (c *Client) GetLastAppKeyStates(ctx context.Context, userAddress string, op } if opts != nil { req.SessionKey = opts.SessionKey + req.IncludeInactive = opts.IncludeInactive } resp, err := c.rpcClient.AppSessionsV1GetLastKeyStates(ctx, req) @@ -351,12 +384,13 @@ func (c *Client) GetLastAppKeyStates(ctx context.Context, userAddress string, op return states, nil } -// SignSessionKeyState signs a session key state using the client's state signer. -// This creates a properly formatted signature that can be set on the state's UserSig field -// before submitting via SubmitSessionKeyState. +// SignSessionKeyState produces the wallet UserSig over the session key state using the +// client's state signer. Set the returned hex on state.UserSig before submit. The matching +// session-key-holder SessionKeySig must also be populated (see SignAppSessionKeyOwnership) +// — submits with only one of the two are rejected. // // Parameters: -// - state: The session key state to sign (UserSig field is excluded from signing) +// - state: The session key state to sign (UserSig and SessionKeySig fields are excluded from signing) // // Returns: // - The hex-encoded signature string @@ -372,9 +406,9 @@ func (c *Client) GetLastAppKeyStates(ctx context.Context, userAddress string, op // AppSessionIDs: []string{}, // ExpiresAt: time.Now().Add(24 * time.Hour), // } -// sig, err := client.SignSessionKeyState(state) -// state.UserSig = sig -// err = client.SubmitSessionKeyState(ctx, state) +// state.UserSig, _ = client.SignSessionKeyState(state) +// state.SessionKeySig, _ = sdk.SignAppSessionKeyOwnership(state, sessionKeySigner) +// err = client.SubmitAppSessionKeyState(ctx, state) func (c *Client) SignSessionKeyState(state app.AppSessionKeyStateV1) (string, error) { packed, err := app.PackAppSessionKeyStateV1(state) if err != nil { @@ -389,3 +423,26 @@ func (c *Client) SignSessionKeyState(state app.AppSessionKeyStateV1) (string, er // Strip the channel signer type prefix byte; session key auth uses plain EIP-191 signatures return hexutil.Encode(sig[1:]), nil } + +// SignAppSessionKeyOwnership produces the session-key holder's ownership signature over the +// packed app-session key state. The signer must be the holder of the session key being +// registered; the resulting hex-encoded signature is intended to populate state.SessionKeySig +// before submitting via SubmitAppSessionKeyState. The packed state already binds user_address, +// so replay across wallets is not possible. +// +// The parameter is narrowed to *sign.EthereumMsgSigner because the server recovers +// SessionKeySig under sign.TypeEthereumMsg — a broader signer interface could produce a +// signature without the EIP-191 prefix (or with extra wrapper bytes) that the server rejects. +func SignAppSessionKeyOwnership(state app.AppSessionKeyStateV1, sessionKeySigner *sign.EthereumMsgSigner) (string, error) { + packed, err := app.PackAppSessionKeyStateV1(state) + if err != nil { + return "", fmt.Errorf("failed to pack session key state: %w", err) + } + + sig, err := sessionKeySigner.Sign(packed) + if err != nil { + return "", fmt.Errorf("failed to sign session key ownership: %w", err) + } + + return hexutil.Encode(sig), nil +} diff --git a/sdk/go/asset_cache.go b/sdk/go/asset_cache.go index 4b710dd81..b2b61bbdc 100644 --- a/sdk/go/asset_cache.go +++ b/sdk/go/asset_cache.go @@ -8,7 +8,8 @@ import ( "github.com/layer-3/nitrolite/pkg/core" ) -// clientAssetStore implements core.AssetStore by fetching data from the Clearnode API. +// TODO: refactor by not using client as a dependency as this creates a circular usage. +// clientAssetStore implements core.AssetStore by fetching data from the Nitronode API. type clientAssetStore struct { client *Client cache map[string]core.Asset // lowercase asset symbol -> Asset @@ -28,12 +29,18 @@ func (s *clientAssetStore) populateCache() error { return fmt.Errorf("failed to fetch assets: %w", err) } for _, a := range assets { + for _, token := range a.Tokens { + if a.Decimals > token.Decimals { + return fmt.Errorf("asset %s decimals (%d) must not exceed token %s decimals (%d) on blockchain %d", + a.Symbol, a.Decimals, token.Symbol, token.Decimals, token.BlockchainID) + } + } s.cache[strings.ToLower(a.Symbol)] = a } return nil } -// GetAssetDecimals returns the decimals for an asset as stored in Clearnode. +// GetAssetDecimals returns the decimals for an asset as stored in Nitronode. func (s *clientAssetStore) GetAssetDecimals(asset string) (uint8, error) { key := strings.ToLower(asset) @@ -77,6 +84,29 @@ func (s *clientAssetStore) GetTokenDecimals(blockchainID uint64, tokenAddress st return 0, fmt.Errorf("token %s on blockchain %d not found", tokenAddress, blockchainID) } +// GetTokenAsset returns the asset for a specific token on a blockchain. +func (s *clientAssetStore) GetTokenAsset(blockchainID uint64, tokenAddress string) (string, error) { + // Fetch all assets if cache is empty + if len(s.cache) == 0 { + if err := s.populateCache(); err != nil { + return "", err + } + } + + // Search through all assets for matching token + tokenAddressLower := strings.ToLower(tokenAddress) + for _, asset := range s.cache { + for _, token := range asset.Tokens { + if token.BlockchainID == blockchainID && + strings.EqualFold(token.Address, tokenAddressLower) { + return asset.Symbol, nil + } + } + } + + return "", fmt.Errorf("token %s on blockchain %d not found", tokenAddress, blockchainID) +} + // GetTokenAddress returns the token address for a given asset on a specific blockchain. func (s *clientAssetStore) GetTokenAddress(asset string, blockchainID uint64) (string, error) { key := strings.ToLower(asset) diff --git a/sdk/go/asset_cache_test.go b/sdk/go/asset_cache_test.go index 465f97410..59c48c04c 100644 --- a/sdk/go/asset_cache_test.go +++ b/sdk/go/asset_cache_test.go @@ -135,6 +135,43 @@ func TestClientAssetStore_Caching(t *testing.T) { require.NoError(t, err) } +func TestClientAssetStore_DecimalsValidation(t *testing.T) { + t.Parallel() + mockDialer := NewMockDialer() + mockDialer.Dial(context.Background(), "", nil) + + mockResp := rpc.NodeV1GetAssetsResponse{ + Assets: []rpc.AssetV1{ + { + Name: "USDC", + Symbol: "USDC", + Decimals: 18, + SuggestedBlockchainID: "137", + Tokens: []rpc.TokenV1{ + { + BlockchainID: "137", + Address: "0xToken137", + Name: "USDC (Polygon)", + Symbol: "USDC", + Decimals: 6, + }, + }, + }, + }, + } + mockDialer.RegisterResponse(rpc.NodeV1GetAssetsMethod.String(), mockResp) + + rpcClient := rpc.NewClient(mockDialer) + client := &Client{ + rpcClient: rpcClient, + } + store := newClientAssetStore(client) + + _, err := store.GetAssetDecimals("USDC") + require.Error(t, err) + assert.Contains(t, err.Error(), "asset USDC decimals (18) must not exceed token USDC decimals (6)") +} + func TestDefaultWebsocketDialerConfig(t *testing.T) { t.Parallel() assert.Equal(t, 5*time.Second, rpc.DefaultWebsocketDialerConfig.HandshakeTimeout) diff --git a/sdk/go/channel.go b/sdk/go/channel.go index f190a3e2e..9f6050dff 100644 --- a/sdk/go/channel.go +++ b/sdk/go/channel.go @@ -56,9 +56,12 @@ func (c *Client) Deposit(ctx context.Context, blockchainID uint64, asset string, // Try to get latest state to determine if channel exists state, err := c.GetLatestState(ctx, userWallet, asset, false) + if err != nil { + return nil, fmt.Errorf("failed to get latest state: %w", err) + } // Scenario A: Channel doesn't exist - create it - if err != nil || state.HomeChannelID == nil { + if state == nil || state.HomeChannelID == nil || state.IsFinal() { // Get supported sig validators bitmap from node config bitmap, err := c.getSupportedSigValidatorsBitmap(ctx) if err != nil { @@ -89,7 +92,7 @@ func (c *Client) Deposit(ctx context.Context, blockchainID uint64, asset string, } // Sign state - sig, err := c.SignState(newState) + sig, err := c.ValidateAndSignState(state, newState) if err != nil { return nil, fmt.Errorf("failed to sign state: %w", err) } @@ -103,6 +106,11 @@ func (c *Client) Deposit(ctx context.Context, blockchainID uint64, asset string, newState.NodeSig = &nodeSig return newState, nil + } else if state.HomeLedger.BlockchainID != blockchainID { + // Active home channel is bound to the chain it was created on. + // Silently advancing it onto a different chain would surface as a + // confusing on-chain failure later. + return nil, fmt.Errorf("active home channel for asset %q is on chain %d, cannot deposit on chain %d", asset, state.HomeLedger.BlockchainID, blockchainID) } // Scenario B: Channel exists - checkpoint deposit @@ -116,7 +124,7 @@ func (c *Client) Deposit(ctx context.Context, blockchainID uint64, asset string, } // Sign and submit state to node - _, err = c.signAndSubmitState(ctx, nextState) + _, err = c.signAndSubmitState(ctx, state, nextState) if err != nil { return nextState, err } @@ -164,9 +172,12 @@ func (c *Client) Withdraw(ctx context.Context, blockchainID uint64, asset string // Try to get latest state to determine if channel exists state, err := c.GetLatestState(ctx, userWallet, asset, false) + if err != nil { + return nil, fmt.Errorf("failed to get latest state: %w", err) + } // Channel doesn't exist - create it and withdraw - if err != nil || state.HomeChannelID == nil { + if state == nil || state.HomeChannelID == nil || state.IsFinal() { // Get supported sig validators bitmap from node config bitmap, err := c.getSupportedSigValidatorsBitmap(ctx) if err != nil { @@ -199,7 +210,7 @@ func (c *Client) Withdraw(ctx context.Context, blockchainID uint64, asset string } // Sign state - sig, err := c.SignState(newState) + sig, err := c.ValidateAndSignState(state, newState) if err != nil { return nil, fmt.Errorf("failed to sign state: %w", err) } @@ -213,6 +224,11 @@ func (c *Client) Withdraw(ctx context.Context, blockchainID uint64, asset string newState.NodeSig = &nodeSig return newState, nil + } else if state.HomeLedger.BlockchainID != blockchainID { + // Active home channel is bound to the chain it was created on. + // Silently advancing it onto a different chain would surface as a + // confusing on-chain failure later. + return nil, fmt.Errorf("active home channel for asset %q is on chain %d, cannot withdraw on chain %d", asset, state.HomeLedger.BlockchainID, blockchainID) } // Create next state @@ -225,7 +241,7 @@ func (c *Client) Withdraw(ctx context.Context, blockchainID uint64, asset string } // Sign and submit state to node - _, err = c.signAndSubmitState(ctx, nextState) + _, err = c.signAndSubmitState(ctx, state, nextState) if err != nil { return nil, err } @@ -260,7 +276,10 @@ func (c *Client) Transfer(ctx context.Context, recipientWallet string, asset str // Get sender's latest state senderWallet := c.GetUserAddress() state, err := c.GetLatestState(ctx, senderWallet, asset, false) - if err != nil || state.HomeChannelID == nil { + if err != nil { + return nil, fmt.Errorf("failed to get latest state: %w", err) + } + if state == nil || state.HomeChannelID == nil || state.IsFinal() { // Get supported sig validators bitmap from node config bitmap, err := c.getSupportedSigValidatorsBitmap(ctx) if err != nil { @@ -314,7 +333,7 @@ func (c *Client) Transfer(ctx context.Context, recipientWallet string, asset str return nil, fmt.Errorf("failed to apply transfer transition: %w", err) } - sig, err := c.SignState(newState) + sig, err := c.ValidateAndSignState(state, newState) if err != nil { return nil, fmt.Errorf("failed to sign state: %w", err) } @@ -340,7 +359,7 @@ func (c *Client) Transfer(ctx context.Context, recipientWallet string, asset str } // Sign and submit state - _, err = c.signAndSubmitState(ctx, nextState) + _, err = c.signAndSubmitState(ctx, state, nextState) if err != nil { return nil, err } @@ -380,7 +399,7 @@ func (c *Client) CloseHomeChannel(ctx context.Context, asset string) (*core.Stat return nil, err } - if state.HomeChannelID == nil { + if state == nil || state.HomeChannelID == nil || state.IsFinal() { return nil, fmt.Errorf("no channel exists for asset %s", asset) } @@ -394,7 +413,7 @@ func (c *Client) CloseHomeChannel(ctx context.Context, asset string) (*core.Stat } // Sign and submit state - _, err = c.signAndSubmitState(ctx, nextState) + _, err = c.signAndSubmitState(ctx, state, nextState) if err != nil { return nil, err } @@ -431,9 +450,12 @@ func (c *Client) Acknowledge(ctx context.Context, asset string) (*core.State, er // Try to get latest state to determine if channel exists state, err := c.GetLatestState(ctx, userWallet, asset, false) + if err != nil { + return nil, fmt.Errorf("failed to get latest state: %w", err) + } // No channel path - create channel with acknowledgement - if err != nil || state.HomeChannelID == nil { + if state == nil || state.HomeChannelID == nil || state.IsFinal() { // Get supported sig validators bitmap from node config bitmap, err := c.getSupportedSigValidatorsBitmap(ctx) if err != nil { @@ -483,7 +505,7 @@ func (c *Client) Acknowledge(ctx context.Context, asset string) (*core.State, er return nil, fmt.Errorf("failed to apply acknowledgement transition: %w", err) } - sig, err := c.SignState(newState) + sig, err := c.ValidateAndSignState(state, newState) if err != nil { return nil, fmt.Errorf("failed to sign state: %w", err) } @@ -510,7 +532,7 @@ func (c *Client) Acknowledge(ctx context.Context, asset string) (*core.State, er return nil, fmt.Errorf("failed to apply acknowledgement transition: %w", err) } - _, err = c.signAndSubmitState(ctx, nextState) + _, err = c.signAndSubmitState(ctx, state, nextState) if err != nil { return nil, err } @@ -558,6 +580,10 @@ func (c *Client) Checkpoint(ctx context.Context, asset string) (string, error) { return "", fmt.Errorf("failed to get latest signed state: %w", err) } + if state == nil { + return "", fmt.Errorf("no signed state exists for asset %s", asset) + } + if state.HomeChannelID == nil { // NOTE: this should never happen, because signed state MUST have a channel ID return "", fmt.Errorf("no channel exists for asset %s", asset) @@ -576,6 +602,11 @@ func (c *Client) Checkpoint(ctx context.Context, asset string) (string, error) { if err != nil { return "", fmt.Errorf("failed to get home channel: %w", err) } + if channel == nil { + // Signed state existed but home channel record is missing — node is in + // an inconsistent state. + return "", fmt.Errorf("home channel missing for asset %s despite signed state", asset) + } switch state.Transition.Type { case core.TransitionTypeAcknowledgement, @@ -717,18 +748,22 @@ func (c *Client) Challenge(ctx context.Context, state core.State) (string, error // GetHomeChannel retrieves home channel information for a user's asset. // +// Returns (nil, nil) when no home channel exists for the wallet/asset pair — +// absence is a successful response, not an error. +// // Parameters: // - wallet: The user's wallet address // - asset: The asset symbol // // Returns: -// - Channel information for the home channel +// - Channel information for the home channel, or nil if absent // - Error if the request fails // // Example: // // channel, err := client.GetHomeChannel(ctx, "0x1234...", "usdc") -// fmt.Printf("Home Channel: %s (Version: %d)\n", channel.ChannelID, channel.StateVersion) +// if err != nil { return err } +// if channel == nil { /* no channel yet */ } func (c *Client) GetHomeChannel(ctx context.Context, wallet, asset string) (*core.Channel, error) { req := rpc.ChannelsV1GetHomeChannelRequest{ Wallet: wallet, @@ -739,7 +774,11 @@ func (c *Client) GetHomeChannel(ctx context.Context, wallet, asset string) (*cor return nil, fmt.Errorf("failed to get home channel: %w", err) } - channel, err := transformChannel(resp.Channel) + if resp.Channel == nil { + return nil, nil + } + + channel, err := transformChannel(*resp.Channel) if err != nil { return nil, fmt.Errorf("failed to transform channel: %w", err) } @@ -748,17 +787,26 @@ func (c *Client) GetHomeChannel(ctx context.Context, wallet, asset string) (*cor // GetEscrowChannel retrieves escrow channel information for a specific channel ID. // +// Returns (nil, nil) when no escrow channel exists for the given ID — absence +// is a successful response, not an error. +// // Parameters: // - escrowChannelID: The escrow channel ID to query // // Returns: -// - Channel information for the escrow channel +// - Channel information for the escrow channel, or nil if absent // - Error if the request fails // +// Note: when the escrow channel has been closed by the on-chain purge queue +// (no signed FINALIZE_ESCROW_DEPOSIT was received before expiry), StateVersion +// on the returned channel reflects the initiate version (N) and does not advance +// to the finalize version (N+1). +// // Example: // // channel, err := client.GetEscrowChannel(ctx, "0x1234...") -// fmt.Printf("Escrow Channel: %s (Version: %d)\n", channel.ChannelID, channel.StateVersion) +// if err != nil { return err } +// if channel == nil { /* not found */ } func (c *Client) GetEscrowChannel(ctx context.Context, escrowChannelID string) (*core.Channel, error) { req := rpc.ChannelsV1GetEscrowChannelRequest{ EscrowChannelID: escrowChannelID, @@ -768,7 +816,11 @@ func (c *Client) GetEscrowChannel(ctx context.Context, escrowChannelID string) ( return nil, fmt.Errorf("failed to get escrow channel: %w", err) } - channel, err := transformChannel(resp.Channel) + if resp.Channel == nil { + return nil, nil + } + + channel, err := transformChannel(*resp.Channel) if err != nil { return nil, fmt.Errorf("failed to transform channel: %w", err) } @@ -781,19 +833,23 @@ func (c *Client) GetEscrowChannel(ctx context.Context, escrowChannelID string) ( // GetLatestState retrieves the latest state for a user's asset. // +// Returns (nil, nil) when the node has no stored state for the wallet/asset +// pair — absence is a successful response, not an error. +// // Parameters: // - wallet: The user's wallet address // - asset: The asset symbol (e.g., "usdc") // - onlySigned: If true, returns only the latest signed state // // Returns: -// - core.State containing all state information +// - core.State containing all state information, or nil if absent // - Error if the request fails // // Example: // // state, err := client.GetLatestState(ctx, "0x1234...", "usdc", false) -// fmt.Printf("State Version: %d, Balance: %s\n", state.Version, state.HomeLedger.UserBalance) +// if err != nil { return err } +// if state == nil { /* no state yet */ } func (c *Client) GetLatestState(ctx context.Context, wallet, asset string, onlySigned bool) (*core.State, error) { req := rpc.ChannelsV1GetLatestStateRequest{ Wallet: wallet, @@ -804,7 +860,10 @@ func (c *Client) GetLatestState(ctx context.Context, wallet, asset string, onlyS if err != nil { return nil, fmt.Errorf("failed to get latest state: %w", err) } - state, err := transformState(resp.State) + if resp.State == nil { + return nil, nil + } + state, err := transformState(*resp.State) if err != nil { return nil, fmt.Errorf("failed to transform state: %w", err) } @@ -848,10 +907,25 @@ func (c *Client) requestChannelCreation(ctx context.Context, state core.State, c type GetLastChannelKeyStatesOptions struct { // SessionKey filters by a specific session key address SessionKey *string + // IncludeInactive, when set to true, includes latest states whose expires_at is in + // the past (expired or revoked). Defaults to false (active-only) when nil or false. + IncludeInactive *bool } -// SubmitChannelSessionKeyState submits a channel session key state for registration or update. -// The state must be signed by the user's wallet to authorize the session key delegation. +// SubmitChannelSessionKeyState submits a channel session key state for registration, +// update, revocation, or re-activation. The state must carry both the wallet's UserSig +// (authorizing the delegation) and the session-key holder's SessionKeySig (proving +// possession of the key); submits without a valid SessionKeySig are rejected on every +// path, including revocation — the session key must co-sign its own deactivation. +// Wallet-only revocation (for a lost or compromised key) is not supported by this +// method. +// +// Set state.ExpiresAt to a future time to register or update the key. Set it to a +// value at or before time.Now() to revoke the key — the auth path filters by +// expires_at, so the key is deactivated immediately while keeping the same monotonic +// version sequence. A later submit with the next version and a future ExpiresAt +// re-activates the same session key address. Negative unix timestamps are rejected +// by the server. // // Parameters: // - state: The channel session key state containing delegation information @@ -867,8 +941,9 @@ type GetLastChannelKeyStatesOptions struct { // Version: 1, // Assets: []string{"usdc", "weth"}, // ExpiresAt: time.Now().Add(24 * time.Hour), -// UserSig: "0x...", // } +// state.UserSig, _ = client.SignChannelSessionKeyState(state) +// state.SessionKeySig, _ = sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner) // err := client.SubmitChannelSessionKeyState(ctx, state) func (c *Client) SubmitChannelSessionKeyState(ctx context.Context, state core.ChannelSessionKeyStateV1) error { req := rpc.ChannelsV1SubmitSessionKeyStateRequest{ @@ -882,13 +957,15 @@ func (c *Client) SubmitChannelSessionKeyState(ctx context.Context, state core.Ch } // GetLastChannelKeyStates retrieves the latest channel session key states for a user. +// By default only currently active (non-expired) states are returned; set +// opts.IncludeInactive to true to include expired or revoked latest states. // // Parameters: // - userAddress: The user's wallet address -// - opts: Optional filters (pass nil for no filters) +// - opts: Optional filters (pass nil for active-only with no session-key filter) // // Returns: -// - Slice of ChannelSessionKeyStateV1 with the latest non-expired session key states +// - Slice of ChannelSessionKeyStateV1 with the latest session key states matching the filter // - Error if the request fails // // Example: @@ -903,6 +980,7 @@ func (c *Client) GetLastChannelKeyStates(ctx context.Context, userAddress string } if opts != nil { req.SessionKey = opts.SessionKey + req.IncludeInactive = opts.IncludeInactive } resp, err := c.rpcClient.ChannelsV1GetLastKeyStates(ctx, req) @@ -918,12 +996,13 @@ func (c *Client) GetLastChannelKeyStates(ctx context.Context, userAddress string return states, nil } -// SignChannelSessionKeyState signs a channel session key state using the client's state signer. -// This creates a properly formatted signature that can be set on the state's UserSig field -// before submitting via SubmitChannelSessionKeyState. +// SignChannelSessionKeyState produces the wallet UserSig over the channel session key +// state using the client's state signer. Set the returned hex on state.UserSig before +// submit. The matching session-key-holder SessionKeySig must also be populated (see +// SignChannelSessionKeyOwnership) — submits with only one of the two are rejected. // // Parameters: -// - state: The channel session key state to sign (UserSig field is excluded from signing) +// - state: The channel session key state to sign (UserSig and SessionKeySig fields are excluded from signing) // // Returns: // - The hex-encoded signature string @@ -938,11 +1017,11 @@ func (c *Client) GetLastChannelKeyStates(ctx context.Context, userAddress string // Assets: []string{"usdc"}, // ExpiresAt: time.Now().Add(24 * time.Hour), // } -// sig, err := client.SignChannelSessionKeyState(state) -// state.UserSig = sig +// state.UserSig, _ = client.SignChannelSessionKeyState(state) +// state.SessionKeySig, _ = sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner) // err = client.SubmitChannelSessionKeyState(ctx, state) func (c *Client) SignChannelSessionKeyState(state core.ChannelSessionKeyStateV1) (string, error) { - metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(state.Version, state.Assets, state.ExpiresAt.Unix()) + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(state.UserAddress, state.Version, state.Assets, state.ExpiresAt.Unix()) if err != nil { return "", fmt.Errorf("failed to compute metadata hash: %w", err) } @@ -965,6 +1044,33 @@ func (c *Client) SignChannelSessionKeyState(state core.ChannelSessionKeyStateV1) return sig.String(), nil } +// SignChannelSessionKeyOwnership produces the session-key holder's ownership signature for a +// channel session key registration. The signer must hold the session key; the returned hex +// string populates state.SessionKeySig before submit. The signed payload binds session_key +// into the metadata hash so a signature minted for one key cannot be replayed for another. +// +// The parameter is narrowed to *sign.EthereumMsgSigner because the server recovers +// SessionKeySig under sign.TypeEthereumMsg — a broader signer interface could produce a +// signature without the EIP-191 prefix (or with extra wrapper bytes) that the server rejects. +func SignChannelSessionKeyOwnership(state core.ChannelSessionKeyStateV1, sessionKeySigner *sign.EthereumMsgSigner) (string, error) { + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(state.UserAddress, state.Version, state.Assets, state.ExpiresAt.Unix()) + if err != nil { + return "", fmt.Errorf("failed to compute metadata hash: %w", err) + } + + packed, err := core.PackChannelKeyStateV1(state.SessionKey, metadataHash) + if err != nil { + return "", fmt.Errorf("failed to pack channel session key state: %w", err) + } + + sig, err := sessionKeySigner.Sign(packed) + if err != nil { + return "", fmt.Errorf("failed to sign channel session key ownership: %w", err) + } + + return sig.String(), nil +} + // ApproveToken approves the ChannelHub contract to spend tokens on behalf of the user. // This is required before depositing ERC-20 tokens. Native tokens (e.g., ETH) do not // require approval and will return an error if attempted. diff --git a/sdk/go/client.go b/sdk/go/client.go index f2ef949aa..4cb7820e0 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -17,7 +17,7 @@ import ( "github.com/shopspring/decimal" ) -// Client provides a unified interface for interacting with Clearnode. +// Client provides a unified interface for interacting with Nitronode. // It combines state-building operations (Deposit, Withdraw, Transfer) with a single // Checkpoint method for blockchain settlement, plus low-level RPC access for advanced use cases. // @@ -30,7 +30,7 @@ import ( // stateSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex) // txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex) // client, _ := sdk.NewClient( -// "wss://clearnode-sandbox.yellow.org/v1/ws", +// "wss://nitronode-sandbox.yellow.org/v1/ws", // stateSigner, // txSigner, // sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"), @@ -56,16 +56,17 @@ type Client struct { blockchainClients map[uint64]core.BlockchainClient blockchainLockingClients map[uint64]*evm.LockingClient homeBlockchains map[string]uint64 + stateAdvancer core.StateAdvancer stateSigner core.ChannelSigner rawSigner sign.Signer assetStore *clientAssetStore } -// NewClient creates a new Clearnode client with both high-level and low-level methods. +// NewClient creates a new Nitronode 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-sandbox.yellow.org/v1/ws") +// - wsURL: WebSocket URL of the Nitronode server (e.g., "wss://nitronode-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.) @@ -79,7 +80,7 @@ type Client struct { // stateSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex) // txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex) // client, err := sdk.NewClient( -// "wss://clearnode-sandbox.yellow.org/v1/ws", +// "wss://nitronode-sandbox.yellow.org/v1/ws", // stateSigner, // txSigner, // sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"), @@ -102,6 +103,11 @@ func NewClient(wsURL string, stateSigner core.ChannelSigner, rawSigner sign.Sign dialer := rpc.NewWebsocketDialer(dialerConfig) rpcClient := rpc.NewClient(dialer) + dialURL, err := appendApplicationIDQueryParam(wsURL, config.ApplicationID) + if err != nil { + return nil, fmt.Errorf("invalid websocket URL: %w", err) + } + // Create client instance client := &Client{ rpcClient: rpcClient, @@ -116,6 +122,7 @@ func NewClient(wsURL string, stateSigner core.ChannelSigner, rawSigner sign.Sign // Create asset store client.assetStore = newClientAssetStore(client) + client.stateAdvancer = core.NewStateAdvancerV1(client.assetStore) // Error handler wrapper handleError := func(err error) { @@ -126,9 +133,9 @@ func NewClient(wsURL string, stateSigner core.ChannelSigner, rawSigner sign.Sign } // Establish connection - err := rpcClient.Start(context.Background(), wsURL, handleError) + err = rpcClient.Start(context.Background(), dialURL, handleError) if err != nil { - return nil, fmt.Errorf("failed to connect to clearnode: %w", err) + return nil, fmt.Errorf("failed to connect to nitronode: %w", err) } return client, nil @@ -215,19 +222,24 @@ func (c *Client) WaitCh() <-chan struct{} { // Shared Helper Methods // ============================================================================ -// SignState signs a channel state by packing it, hashing it, and signing the hash. +// ValidateAndSignState firstly validates and then signs a channel state by packing it, hashing it, and signing the hash. // Returns the signature as a hex-encoded string (with 0x prefix). // // This is a low-level method exposed for advanced users who want to manually // construct and sign states. Most users should use the high-level methods like // Transfer, Deposit, and Withdraw instead. -func (c *Client) SignState(state *core.State) (string, error) { - if state == nil { - return "", fmt.Errorf("state cannot be nil") +func (c *Client) ValidateAndSignState(currentState, proposedState *core.State) (string, error) { + if currentState == nil || proposedState == nil { + return "", fmt.Errorf("current or proposed state cannot be nil") + } + + // Validate the state + if err := c.stateAdvancer.ValidateAdvancement(*currentState, *proposedState); err != nil { + return "", fmt.Errorf("state validation failed: %w", err) } // Pack the state into ABI-encoded bytes - packedState, err := core.PackState(*state, c.assetStore) + packedState, err := core.PackState(*proposedState, c.assetStore) if err != nil { return "", fmt.Errorf("failed to pack state: %w", err) } @@ -248,24 +260,24 @@ func (c *Client) GetUserAddress() string { return c.rawSigner.PublicKey().Address().String() } -// signAndSubmitState is a helper that signs a state and submits it to the node. +// signAndSubmitState is a helper that validates, signs a state and submits it to the node. // It returns the node's signature. -func (c *Client) signAndSubmitState(ctx context.Context, state *core.State) (string, error) { +func (c *Client) signAndSubmitState(ctx context.Context, currentState, proposedState *core.State) (string, error) { // Sign state - sig, err := c.SignState(state) + sig, err := c.ValidateAndSignState(currentState, proposedState) if err != nil { return "", fmt.Errorf("failed to sign state: %w", err) } - state.UserSig = &sig + proposedState.UserSig = &sig // Submit to node - nodeSig, err := c.submitState(ctx, *state) + nodeSig, err := c.submitState(ctx, *proposedState) if err != nil { return "", fmt.Errorf("failed to submit state: %w", err) } // Update state with node signature - state.NodeSig = &nodeSig + proposedState.NodeSig = &nodeSig return nodeSig, nil } diff --git a/sdk/go/client_test.go b/sdk/go/client_test.go index 39e135f9b..448179aa1 100644 --- a/sdk/go/client_test.go +++ b/sdk/go/client_test.go @@ -23,7 +23,7 @@ func TestClient_GetHomeChannel(t *testing.T) { mockDialer.Dial(context.Background(), "", nil) mockResp := rpc.ChannelsV1GetHomeChannelResponse{ - Channel: rpc.ChannelV1{ + Channel: &rpc.ChannelV1{ ChannelID: "0xChannelID", UserWallet: "0xWallet", Type: "home", @@ -45,13 +45,30 @@ func TestClient_GetHomeChannel(t *testing.T) { assert.Equal(t, core.ChannelTypeHome, ch.Type) } +// TestClient_GetHomeChannel_NilResponse verifies absent-channel responses surface as (nil, nil). +func TestClient_GetHomeChannel_NilResponse(t *testing.T) { + t.Parallel() + mockDialer := NewMockDialer() + mockDialer.Dial(context.Background(), "", nil) + + mockDialer.RegisterResponse(rpc.ChannelsV1GetHomeChannelMethod.String(), rpc.ChannelsV1GetHomeChannelResponse{}) + + client := &Client{ + rpcClient: rpc.NewClient(mockDialer), + } + + ch, err := client.GetHomeChannel(context.Background(), "0xWallet", "USDC") + require.NoError(t, err) + assert.Nil(t, ch) +} + func TestClient_GetEscrowChannel(t *testing.T) { t.Parallel() mockDialer := NewMockDialer() mockDialer.Dial(context.Background(), "", nil) mockResp := rpc.ChannelsV1GetEscrowChannelResponse{ - Channel: rpc.ChannelV1{ + Channel: &rpc.ChannelV1{ ChannelID: "0xEscrowID", UserWallet: "0xWallet", Type: "escrow", @@ -73,13 +90,30 @@ func TestClient_GetEscrowChannel(t *testing.T) { assert.Equal(t, core.ChannelTypeEscrow, ch.Type) } +// TestClient_GetEscrowChannel_NilResponse verifies absent-channel responses surface as (nil, nil). +func TestClient_GetEscrowChannel_NilResponse(t *testing.T) { + t.Parallel() + mockDialer := NewMockDialer() + mockDialer.Dial(context.Background(), "", nil) + + mockDialer.RegisterResponse(rpc.ChannelsV1GetEscrowChannelMethod.String(), rpc.ChannelsV1GetEscrowChannelResponse{}) + + client := &Client{ + rpcClient: rpc.NewClient(mockDialer), + } + + ch, err := client.GetEscrowChannel(context.Background(), "0xEscrowID") + require.NoError(t, err) + assert.Nil(t, ch) +} + func TestClient_GetLatestState(t *testing.T) { t.Parallel() mockDialer := NewMockDialer() mockDialer.Dial(context.Background(), "", nil) mockResp := rpc.ChannelsV1GetLatestStateResponse{ - State: rpc.StateV1{ + State: &rpc.StateV1{ ID: "0xStateID", Epoch: "1", Version: "1", @@ -110,6 +144,23 @@ func TestClient_GetLatestState(t *testing.T) { assert.Equal(t, uint64(1), state.Version) } +// TestClient_GetLatestState_NilResponse verifies absent-state responses surface as (nil, nil). +func TestClient_GetLatestState_NilResponse(t *testing.T) { + t.Parallel() + mockDialer := NewMockDialer() + mockDialer.Dial(context.Background(), "", nil) + + mockDialer.RegisterResponse(rpc.ChannelsV1GetLatestStateMethod.String(), rpc.ChannelsV1GetLatestStateResponse{}) + + client := &Client{ + rpcClient: rpc.NewClient(mockDialer), + } + + state, err := client.GetLatestState(context.Background(), "0xWallet", "USDC", false) + require.NoError(t, err) + assert.Nil(t, state) +} + func TestClient_GetBalances(t *testing.T) { t.Parallel() mockDialer := NewMockDialer() @@ -117,7 +168,7 @@ func TestClient_GetBalances(t *testing.T) { mockResp := rpc.UserV1GetBalancesResponse{ Balances: []rpc.BalanceEntryV1{ - {Asset: "USDC", Amount: "100.0"}, + {Asset: "USDC", Amount: "100.0", Enforced: "0"}, }, } mockDialer.RegisterResponse(rpc.UserV1GetBalancesMethod.String(), mockResp) @@ -198,7 +249,7 @@ func TestClient_GetAppDefinition(t *testing.T) { mockDialer.Dial(context.Background(), "", nil) mockResp := rpc.AppSessionsV1GetAppDefinitionResponse{ - Definition: rpc.AppDefinitionV1{ + Definition: &rpc.AppDefinitionV1{ Application: "0xApp", Participants: []rpc.AppParticipantV1{}, Nonce: "1", @@ -217,6 +268,23 @@ func TestClient_GetAppDefinition(t *testing.T) { assert.Equal(t, uint64(1), def.Nonce) } +// TestClient_GetAppDefinition_NilResponse verifies absent-definition responses surface as (nil, nil). +func TestClient_GetAppDefinition_NilResponse(t *testing.T) { + t.Parallel() + mockDialer := NewMockDialer() + mockDialer.Dial(context.Background(), "", nil) + + mockDialer.RegisterResponse(rpc.AppSessionsV1GetAppDefinitionMethod.String(), rpc.AppSessionsV1GetAppDefinitionResponse{}) + + client := &Client{ + rpcClient: rpc.NewClient(mockDialer), + } + + def, err := client.GetAppDefinition(context.Background(), "0xSessionID") + require.NoError(t, err) + assert.Nil(t, def) +} + func TestClient_CreateAppSession(t *testing.T) { t.Parallel() mockDialer := NewMockDialer() @@ -264,7 +332,7 @@ func TestClient_SubmitAppSessionDeposit(t *testing.T) { Decimals: 6, SuggestedBlockchainID: "137", Tokens: []rpc.TokenV1{ - {BlockchainID: "137", Address: "0xToken"}, + {BlockchainID: "137", Address: "0xToken", Decimals: 6}, }, }, }, @@ -274,7 +342,7 @@ func TestClient_SubmitAppSessionDeposit(t *testing.T) { homeChannelID := "0xHomeChannel" // Mock latest state stateResp := rpc.ChannelsV1GetLatestStateResponse{ - State: rpc.StateV1{ + State: &rpc.StateV1{ ID: "0xStateID", Epoch: "1", Version: "1", @@ -288,8 +356,8 @@ func TestClient_SubmitAppSessionDeposit(t *testing.T) { HomeLedger: rpc.LedgerV1{ BlockchainID: "137", TokenAddress: "0xToken", - UserBalance: "100.0", - UserNetFlow: "0", + UserBalance: "100", + UserNetFlow: "100", NodeBalance: "0", NodeNetFlow: "0", }, @@ -323,6 +391,7 @@ func TestClient_SubmitAppSessionDeposit(t *testing.T) { rawSigner: rawSigner, } client.assetStore = newClientAssetStore(client) + client.stateAdvancer = core.NewStateAdvancerV1(client.assetStore) appUpdate := app.AppStateUpdateV1{ AppSessionID: "0xSessionID", @@ -551,3 +620,104 @@ func TestClient_SignSessionKeyState(t *testing.T) { require.NoError(t, err) assert.Equal(t, rawSigner.PublicKey().Address().String(), recoveredAddr.String()) } + +// newCrossChainTestClient builds a Client wired to a mockDialer pre-stocked +// with the responses needed to reach the Deposit/Withdraw cross-chain guard: +// node config (chain 137 home), assets (asset on both 137 and 8453), and a +// latest state representing an open channel on chain 137. Returns the client +// and the wallet address used to populate the state. +func newCrossChainTestClient(t *testing.T) (*Client, string) { + t.Helper() + + pk, err := crypto.GenerateKey() + require.NoError(t, err) + pkHex := hexutil.Encode(crypto.FromECDSA(pk)) + + rawSigner, err := sign.NewEthereumRawSigner(pkHex) + require.NoError(t, err) + msgSigner, err := sign.NewEthereumMsgSignerFromRaw(rawSigner) + require.NoError(t, err) + stateSigner, err := core.NewChannelDefaultSigner(msgSigner) + require.NoError(t, err) + walletAddr := rawSigner.PublicKey().Address().String() + + mockDialer := NewMockDialer() + mockDialer.Dial(context.Background(), "", nil) + + mockDialer.RegisterResponse(rpc.NodeV1GetConfigMethod.String(), rpc.NodeV1GetConfigResponse{ + NodeAddress: "0xNodeAddress", + Blockchains: []rpc.BlockchainInfoV1{ + {Name: "Polygon", BlockchainID: "137", ChannelHubAddress: "0xHubAddr137"}, + {Name: "Base", BlockchainID: "8453", ChannelHubAddress: "0xHubAddr8453"}, + }, + }) + + mockDialer.RegisterResponse(rpc.NodeV1GetAssetsMethod.String(), rpc.NodeV1GetAssetsResponse{ + Assets: []rpc.AssetV1{ + { + Name: "USDC", + Symbol: "USDC", + Decimals: 6, + SuggestedBlockchainID: "137", + Tokens: []rpc.TokenV1{ + {BlockchainID: "137", Address: "0xToken137", Decimals: 6}, + {BlockchainID: "8453", Address: "0xToken8453", Decimals: 6}, + }, + }, + }, + }) + + homeChannelID := "0xHomeChannel" + mockDialer.RegisterResponse(rpc.ChannelsV1GetLatestStateMethod.String(), rpc.ChannelsV1GetLatestStateResponse{ + State: &rpc.StateV1{ + ID: "0xStateID", + Epoch: "1", + Version: "1", + UserWallet: walletAddr, + Asset: "USDC", + HomeChannelID: &homeChannelID, + Transition: rpc.TransitionV1{ + Type: core.TransitionTypeHomeDeposit, + Amount: "10", + }, + HomeLedger: rpc.LedgerV1{ + BlockchainID: "137", + TokenAddress: "0xToken137", + UserBalance: "10", + UserNetFlow: "10", + NodeBalance: "0", + NodeNetFlow: "0", + }, + }, + }) + + client := &Client{ + rpcClient: rpc.NewClient(mockDialer), + stateSigner: stateSigner, + rawSigner: rawSigner, + homeBlockchains: make(map[string]uint64), + } + client.assetStore = newClientAssetStore(client) + client.stateAdvancer = core.NewStateAdvancerV1(client.assetStore) + return client, walletAddr +} + +func TestClient_Deposit_RejectsForeignChain(t *testing.T) { + t.Parallel() + client, _ := newCrossChainTestClient(t) + + _, err := client.Deposit(context.Background(), 8453, "USDC", decimal.NewFromFloat(0.5)) + require.Error(t, err) + assert.Contains(t, err.Error(), "active home channel for asset \"USDC\" is on chain 137") + assert.Contains(t, err.Error(), "cannot deposit on chain 8453") +} + +func TestClient_Withdraw_RejectsForeignChain(t *testing.T) { + t.Parallel() + client, _ := newCrossChainTestClient(t) + + _, err := client.Withdraw(context.Background(), 8453, "USDC", decimal.NewFromFloat(0.5)) + require.Error(t, err) + assert.Contains(t, err.Error(), "active home channel for asset \"USDC\" is on chain 137") + assert.Contains(t, err.Error(), "cannot withdraw on chain 8453") +} diff --git a/sdk/go/config.go b/sdk/go/config.go index 3f3e1bf05..485862a89 100644 --- a/sdk/go/config.go +++ b/sdk/go/config.go @@ -2,13 +2,16 @@ package sdk import ( "log" + "net/url" "os" "time" + + "github.com/layer-3/nitrolite/pkg/rpc" ) -// Config holds the configuration options for the Clearnode client. +// Config holds the configuration options for the Nitronode client. type Config struct { - // URL is the WebSocket URL of the clearnode server + // URL is the WebSocket URL of the nitronode server URL string // HandshakeTimeout is the maximum time to wait for initial connection @@ -24,6 +27,11 @@ type Config struct { // BlockchainRPCs maps blockchain IDs to their RPC endpoints // Used by SDKClient for on-chain operations BlockchainRPCs map[uint64]string + + // ApplicationID is an advisory origin tag the client sends to the nitronode as + // the "app_id" WebSocket query parameter. The nitronode stamps this value on + // records produced by requests from this client. Empty means no tag. + ApplicationID string } // Option is a functional option for configuring the Client. @@ -36,10 +44,27 @@ var DefaultConfig = Config{ ErrorHandler: defaultErrorHandler, } +// appendApplicationIDQueryParam returns wsURL with the app_id query parameter set +// to applicationID. If applicationID is empty, wsURL is returned unchanged. Any +// existing app_id value is overwritten. Returns an error if wsURL cannot be parsed. +func appendApplicationIDQueryParam(wsURL, applicationID string) (string, error) { + if applicationID == "" { + return wsURL, nil + } + parsed, err := url.Parse(wsURL) + if err != nil { + return "", err + } + q := parsed.Query() + q.Set(rpc.ApplicationIDQueryParam, applicationID) + parsed.RawQuery = q.Encode() + return parsed.String(), nil +} + // defaultErrorHandler logs errors to stderr. func defaultErrorHandler(err error) { if err != nil { - log.New(os.Stderr, "[clearnode] ", log.LstdFlags).Printf("connection error: %v", err) + log.New(os.Stderr, "[nitronode] ", log.LstdFlags).Printf("connection error: %v", err) } } @@ -57,6 +82,15 @@ func WithPingTimeout(d time.Duration) Option { } } +// WithApplicationID sets the application ID sent to the nitronode as the +// "app_id" WebSocket query parameter. The nitronode treats this as an advisory +// origin tag on records produced by requests from this connection. +func WithApplicationID(appID string) Option { + return func(c *Config) { + c.ApplicationID = appID + } +} + // WithErrorHandler sets a custom error handler for connection errors. // The handler is called when the connection encounters an error or is closed. func WithErrorHandler(fn func(error)) Option { diff --git a/sdk/go/config_test.go b/sdk/go/config_test.go index 939ad8859..d092862e3 100644 --- a/sdk/go/config_test.go +++ b/sdk/go/config_test.go @@ -47,3 +47,37 @@ func TestDefaultErrorHandler(t *testing.T) { defaultErrorHandler(nil) // defaultErrorHandler(errors.New("test error")) // verification would require capturing stderr } + +func TestWithApplicationID(t *testing.T) { + c := &Config{} + WithApplicationID("my-app")(c) + assert.Equal(t, "my-app", c.ApplicationID) +} + +func TestAppendApplicationIDQueryParam(t *testing.T) { + cases := []struct { + name string + url string + appID string + want string + wantErr bool + }{ + {name: "empty app_id returns url unchanged", url: "ws://host/path", appID: "", want: "ws://host/path"}, + {name: "adds app_id when no query", url: "ws://host/path", appID: "my-app", want: "ws://host/path?app_id=my-app"}, + {name: "adds app_id alongside existing query", url: "ws://host/path?foo=bar", appID: "my-app", want: "ws://host/path?app_id=my-app&foo=bar"}, + {name: "overwrites existing app_id", url: "ws://host/path?app_id=old", appID: "new", want: "ws://host/path?app_id=new"}, + {name: "url-escapes the value", url: "ws://host/", appID: "a b&c", want: "ws://host/?app_id=a+b%26c"}, + {name: "invalid url returns error", url: "://", appID: "x", wantErr: true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := appendApplicationIDQueryParam(tc.url, tc.appID) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/sdk/go/doc.go b/sdk/go/doc.go index c15f85390..915e65ec4 100644 --- a/sdk/go/doc.go +++ b/sdk/go/doc.go @@ -1,6 +1,6 @@ -// Package sdk provides the official Go client for the Nitrolite Clearnode API. +// Package sdk provides the official Go client for the Nitrolite Nitronode API. // -// The SDK offers a unified interface for interacting with Clearnode payment channels, +// The SDK offers a unified interface for interacting with Nitronode payment channels, // supporting both high-level state operations and low-level RPC access. It simplifies // the process of managing channel states, performing off-chain transactions, and // settling on-chain when necessary. @@ -12,14 +12,14 @@ // to build and co-sign channel states off-chain. // - Blockchain Settlement: A single `Checkpoint` method to settle the latest state on-chain // (creating channels, depositing, withdrawing, or finalizing). -// - Low-Level Access: Direct access to Clearnode RPC methods for advanced use cases +// - Low-Level Access: Direct access to Nitronode RPC methods for advanced use cases // (e.g., querying node config, balances, channel info). // - App Sessions: Comprehensive support for creating and managing application sessions. // - Session Keys: Support for registering and using session keys for delegated signing. // // # Usage // -// To use the SDK, create a `Client` instance with your Clearnode WebSocket URL and signers. +// To use the SDK, create a `Client` instance with your Nitronode WebSocket URL and signers. // You can configure blockchain RPCs for on-chain operations. // // package main @@ -48,7 +48,7 @@ // // // Create Client // client, err := sdk.NewClient( -// "wss://clearnode-sandbox.yellow.org/v1/ws", +// "wss://nitronode-sandbox.yellow.org/v1/ws", // stateSigner, // txSigner, // sdk.WithBlockchainRPC(80002, "https://rpc-endpoint.example.com"), @@ -99,5 +99,5 @@ // // The SDK methods return standard Go errors. Common errors to check for include connection issues, // insufficient balances, or invalid state transitions. Errors from RPC calls often contain -// detailed messages from the Clearnode server. +// detailed messages from the Nitronode server. package sdk diff --git a/sdk/go/examples/app_sessions/lifecycle.go b/sdk/go/examples/app_sessions/lifecycle.go index bc2da6fb4..ce7bbd661 100644 --- a/sdk/go/examples/app_sessions/lifecycle.go +++ b/sdk/go/examples/app_sessions/lifecycle.go @@ -2,27 +2,51 @@ 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) +// Requirements to run this example: +// +// 1. A reachable nitronode WebSocket endpoint (set via wsURL below). +// The default points at the public sandbox. +// +// 2. Three EVM wallets with hex private keys (replace the placeholders below). +// Wallet 3 may be a fresh key — it only receives funds via redistribution. +// +// 3. Minimum off-chain (channel) balances on the node: +// - Wallet 1: 0.0001 YUSD (deposited into Session 1) +// - Wallet 2: 0.00015 YELLOW (deposited into Session 2) +// - Wallet 3: none required (receives funds via redistribution) +// +// An open channel is NOT a hard prerequisite. If a wallet already has +// funds on the node but no acknowledged channel for the asset yet, the +// example calls Acknowledge first to open one. Wallet 3 also needs no +// pre-existing channel; the withdraw step will open/credit its ledger +// automatically. +// +// 4. App registry: if the node was started with the app registry disabled +// (apps.v1 group disabled), the registration step is skipped at runtime +// and app sessions are created against unregistered app IDs. No action +// is required from the operator — the example detects this via a probe +// call to GetApps. // // This example demonstrates: -// 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) +// 1. Register apps in the app registry (skipped if apps.v1 group is disabled) +// 2. Create first app session for wallet 1 +// 3. Deposit YUSD into first app session by wallet 1 +// (auto-opens wallet 1's YUSD channel via Acknowledge if missing) +// 4. Create second app session for wallet 2 with wallet 3 as a participant +// 5. Deposit YELLOW into second app session by wallet 2 +// (auto-opens wallet 2's YELLOW channel via Acknowledge if missing) +// 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). +// Skipped entirely when the app registry is disabled. import ( "context" "fmt" "log" "math/rand" + "strings" "time" "github.com/ethereum/go-ethereum/common/hexutil" @@ -35,9 +59,14 @@ import ( sdk "github.com/layer-3/nitrolite/sdk/go" ) +// appRegistryDisabledMsg is the error fragment returned by the node when the +// apps.v1 RPC group is disabled by configuration. The example uses this to +// decide whether to skip the registration step. +const appRegistryDisabledMsg = "apps.v1 group is disabled" + func main() { ctx := context.Background() - wsURL := "wss://clearnode-sandbox.yellow.org/v1/ws" + wsURL := "wss://nitronode-sandbox.yellow.org/v1/ws" // --- 0. Setup Wallets --- // Replace these strings with your actual hex private keys @@ -120,6 +149,15 @@ func main() { log.Fatal(err) } + // --- Ensure Required Channels Are Open --- + // App session deposits require an acknowledged channel for the asset. + // If the wallet has funds on the node but no channel yet, Acknowledge + // opens it on the fly so the example only assumes a minimum balance. + fmt.Println("=== Ensuring Channels Are Open ===") + ensureChannelOpen(ctx, "Wallet 1", wallet1Client, "yusd") + ensureChannelOpen(ctx, "Wallet 2", wallet2Client, "yellow") + fmt.Println() + // --- 1. Register Apps --- fmt.Println("=== Step 1: Registering Apps ===") @@ -127,15 +165,31 @@ func main() { 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) + // Probe the apps.v1 group via GetApps. If the node has the app registry + // disabled, the probe returns an error containing appRegistryDisabledMsg + // and we skip registration entirely — app sessions can still be created + // against unregistered IDs in that mode. + appRegistryEnabled := true + if _, _, err := wallet1Client.GetApps(ctx, nil); err != nil { + if strings.Contains(err.Error(), appRegistryDisabledMsg) { + appRegistryEnabled = false + fmt.Println("ℹ App registry is disabled on the node — skipping app registration") + } else { + log.Fatalf("Failed to query app registry: %v", 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) + if appRegistryEnabled { + 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) } - fmt.Printf("✓ Registered app: %s (owner approval required)\n\n", app2ID) // --- 2. Create App Session 1 (Single Participant: Wallet 1) --- fmt.Println("=== Step 2: Creating App Session 1 (Wallet 1 only) ===") @@ -161,15 +215,15 @@ func main() { } fmt.Printf("✓ Created App Session 1: %s\n\n", session1ID) - // --- 3. Deposit USDC into Session 1 --- - fmt.Println("=== Step 3: Depositing USDC into Session 1 ===") + // --- 3. Deposit YUSD into Session 1 --- + fmt.Println("=== Step 3: Depositing YUSD into Session 1 ===") session1DepositAmount := decimal.NewFromFloat(0.0001) session1DepositUpdate := app.AppStateUpdateV1{ AppSessionID: session1ID, Intent: app.AppStateUpdateIntentDeposit, Version: 2, - Allocations: []app.AppAllocationV1{{Participant: wallet1Address, Asset: "usdc", Amount: session1DepositAmount}}, + Allocations: []app.AppAllocationV1{{Participant: wallet1Address, Asset: "yusd", Amount: session1DepositAmount}}, } session1DepositRequest, err := app.PackAppStateUpdateV1(session1DepositUpdate) @@ -179,11 +233,11 @@ func main() { appSession1DepositSig, _ := appSession1Signer.Sign(session1DepositRequest) - _, err = wallet1Client.SubmitAppSessionDeposit(ctx, session1DepositUpdate, []string{appSession1DepositSig.String()}, "usdc", session1DepositAmount) + _, err = wallet1Client.SubmitAppSessionDeposit(ctx, session1DepositUpdate, []string{appSession1DepositSig.String()}, "yusd", session1DepositAmount) if err != nil { log.Printf("⚠ Deposit warning: %v", err) } - fmt.Printf("✓ Deposited %s USDC into Session 1\n\n", session1DepositAmount) + fmt.Printf("✓ Deposited %s YUSD into Session 1\n\n", session1DepositAmount) // --- 4. Create App Session 2 (Multi-Party: Wallet 2 & 3) --- fmt.Println("=== Step 4: Creating App Session 2 (Wallet 2 & 3) ===") @@ -207,12 +261,21 @@ func main() { log.Fatal(err) } + // Wallet's UserSig authorizes the delegation. appSessionKey3StateSig, err := wallet3Signer.Sign(packedAppSessionKey3State) if err != nil { log.Fatal(err) } appSessionKey3State.UserSig = appSessionKey3StateSig.String() + // Session-key holder's SessionKeySig proves possession of the key being registered. + // Both signatures are required at submit time. + appSessionKey3OwnershipSig, err := msgSigner3.Sign(packedAppSessionKey3State) + if err != nil { + log.Fatal(err) + } + appSessionKey3State.SessionKeySig = appSessionKey3OwnershipSig.String() + if err := wallet3Client.SubmitAppSessionKeyState(context.Background(), appSessionKey3State); err != nil { log.Fatal(err) } @@ -252,15 +315,15 @@ func main() { } fmt.Printf("✓ Created App Session 2: %s\n\n", session2ID) - // --- 5. Deposit WETH into Session 2 by Wallet 2 --- - fmt.Println("=== Step 5: Depositing WETH into Session 2 ===") + // --- 5. Deposit YELLOW into Session 2 by Wallet 2 --- + fmt.Println("=== Step 5: Depositing YELLOW into Session 2 ===") session2DepositAmount := decimal.NewFromFloat(0.00015) session2DepositUpdate := app.AppStateUpdateV1{ AppSessionID: session2ID, Intent: app.AppStateUpdateIntentDeposit, Version: 2, - Allocations: []app.AppAllocationV1{{Participant: wallet2Address, Asset: "weth", Amount: session2DepositAmount}}, + Allocations: []app.AppAllocationV1{{Participant: wallet2Address, Asset: "yellow", Amount: session2DepositAmount}}, } session2DepositRequest, err := app.PackAppStateUpdateV1(session2DepositUpdate) @@ -271,11 +334,11 @@ func main() { appSession2DepositSig, _ := appSession2Signer.Sign(session2DepositRequest) appSession3DepositSig, _ := appSession3Signer.Sign(session2DepositRequest) - nodeSig, err := wallet2Client.SubmitAppSessionDeposit(ctx, session2DepositUpdate, []string{appSession2DepositSig.String(), appSession3DepositSig.String()}, "weth", session2DepositAmount) + nodeSig, err := wallet2Client.SubmitAppSessionDeposit(ctx, session2DepositUpdate, []string{appSession2DepositSig.String(), appSession3DepositSig.String()}, "yellow", session2DepositAmount) if err != nil { log.Fatal(err) } - fmt.Printf("✓ Deposited %s WETH into Session 2 (Node Sig: %s)\n\n", session2DepositAmount, nodeSig) + fmt.Printf("✓ Deposited %s YELLOW into Session 2 (Node Sig: %s)\n\n", session2DepositAmount, nodeSig) // Check Session 2 state before redistribution session2InfoBeforeRedist, _, err := wallet2Client.GetAppSessions(ctx, &sdk.GetAppSessionsOptions{AppSessionID: &session2ID}) @@ -294,8 +357,8 @@ func main() { Intent: app.AppStateUpdateIntentOperate, Version: 3, Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.0001)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00005)}, + {Participant: wallet2Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.0001)}, + {Participant: wallet3Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.00005)}, }, } @@ -312,7 +375,7 @@ func main() { if err != nil { log.Fatalf("Redistribution failed: %v", err) } - fmt.Println("✓ Redistributed WETH: Wallet 2 (0.0001) -> Wallet 3 (0.00005)") + fmt.Println("✓ Redistributed YELLOW: Wallet 2 (0.0001) -> Wallet 3 (0.00005)") // NOTE: Rebalance step is disabled. // // --- 7. Rebalance Both App Sessions Atomically --- @@ -341,8 +404,8 @@ func main() { // 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)}, + // {Participant: wallet1Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.005)}, + // {Participant: wallet1Address, Asset: "yusd", Amount: decimal.NewFromFloat(0.00005)}, // }, // } @@ -358,9 +421,9 @@ func main() { // 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)}, + // {Participant: wallet2Address, Asset: "yusd", Amount: decimal.NewFromFloat(0.00005)}, + // {Participant: wallet2Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.005)}, + // {Participant: wallet3Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.005)}, // }, // } @@ -399,8 +462,8 @@ func main() { Intent: app.AppStateUpdateIntentWithdraw, Version: 4, Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00005)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00001)}, + {Participant: wallet2Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.00005)}, + {Participant: wallet3Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.00001)}, }, } @@ -416,7 +479,7 @@ func main() { if err != nil { log.Printf("⚠ Withdraw Error: %v", err) } else { - fmt.Println("✓ Wallet 3 successfully withdrew WETH back to channel") + fmt.Println("✓ Wallet 3 successfully withdrew YELLOW back to channel") } // --- 8. Close Both App Sessions --- @@ -428,7 +491,7 @@ func main() { Intent: app.AppStateUpdateIntentClose, Version: 3, Allocations: []app.AppAllocationV1{ - {Participant: wallet1Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.0001)}, + {Participant: wallet1Address, Asset: "yusd", Amount: decimal.NewFromFloat(0.0001)}, }, } @@ -452,8 +515,8 @@ func main() { Intent: app.AppStateUpdateIntentClose, Version: 5, Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00005)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00001)}, + {Participant: wallet2Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.00005)}, + {Participant: wallet3Address, Asset: "yellow", Amount: decimal.NewFromFloat(0.00001)}, }, } @@ -473,28 +536,33 @@ func main() { } // --- 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") + // Only meaningful when the app registry is enabled — with apps.v1 disabled + // every app ID is "unregistered" from the registry's perspective and the + // node accepts the create call, so the fail-case has nothing to assert. + if appRegistryEnabled { + 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 ===") @@ -511,3 +579,30 @@ func generateMsgSigner() (sign.Signer, error) { return sign.NewEthereumMsgSigner(hexutil.Encode(privateKeyBytes)) } + +// ensureChannelOpen guarantees that the given wallet has an acknowledged +// channel open for asset. If the node holds no state for the wallet/asset +// pair, or the latest state is still awaiting the user's signature (or has +// been finalized), Acknowledge is invoked to create or progress the channel. +// Already-acknowledged channels are left untouched. +func ensureChannelOpen(ctx context.Context, label string, client *sdk.Client, asset string) { + wallet := client.GetUserAddress() + state, err := client.GetLatestState(ctx, wallet, asset, false) + if err != nil { + log.Fatalf("[%s] failed to get latest %s state: %v", label, asset, err) + } + + hasOpenChannel := state != nil && + state.HomeChannelID != nil && + !state.IsFinal() && + state.UserSig != nil + if hasOpenChannel { + fmt.Printf("✓ %s already has an open %s channel\n", label, asset) + return + } + + if _, err := client.Acknowledge(ctx, asset); err != nil { + log.Fatalf("[%s] failed to acknowledge %s channel: %v", label, asset, err) + } + fmt.Printf("✓ %s acknowledged %s channel\n", label, asset) +} diff --git a/sdk/go/examples/challenge/main.go b/sdk/go/examples/challenge/main.go index cdc9b3da2..a9e829bd5 100644 --- a/sdk/go/examples/challenge/main.go +++ b/sdk/go/examples/challenge/main.go @@ -30,7 +30,7 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - wsURL := "wss://clearnode-sandbox.yellow.org/v1/ws" + wsURL := "wss://nitronode-sandbox.yellow.org/v1/ws" privateKeyHex := "0x7d6..." chainID := uint64(11155111) rpcUrl := "https://sepolia.drpc.org" @@ -90,6 +90,9 @@ func main() { if err != nil { log.Fatalf("Failed to get latest state: %v", err) } + if preTransferState == nil { + log.Fatalf("No signed state available to save") + } fmt.Printf("Saved state at version %d (balance: %s USDC)\n\n", preTransferState.Version, preTransferState.HomeLedger.UserBalance) diff --git a/sdk/go/examples/channel_session_key/lifecycle.go b/sdk/go/examples/channel_session_key/lifecycle.go new file mode 100644 index 000000000..9d51e30e5 --- /dev/null +++ b/sdk/go/examples/channel_session_key/lifecycle.go @@ -0,0 +1,356 @@ +package main + +// Example: Channel Session Key Lifecycle +// +// Requirements to run this example: +// +// 1. A reachable nitronode WebSocket endpoint (set via wsURL below). +// The default points at the stress environment. +// +// 2. One EVM wallet with a hex private key (replace the placeholder below). +// +// 3. Minimum off-chain (channel) balances on the node: +// - 0.00005 YUSD (one deposit + one withdraw via session key) +// - 0.00005 YELLOW (one deposit + one withdraw via session key) +// +// An open channel is NOT a hard prerequisite. If the wallet already has +// funds on the node but no acknowledged channel yet, Acknowledge is run +// first to open one. +// +// 4. chainID below must match the asset's home blockchain for your target +// nitronode deployment, and rpcURL must point at a JSON-RPC endpoint for +// that chain. Both Deposit and Withdraw are followed by an on-chain +// Checkpoint; the example then polls GetHomeChannel until the node has +// observed the checkpoint event before moving on. Without a working RPC +// these calls fail. +// +// This example demonstrates: +// 1. Open YUSD and YELLOW channels for the wallet (Acknowledge) +// 2. Generate a fresh session key +// 3. Register session key v1 with both assets allowed +// 4. Deposit YUSD and YELLOW via a session-key-backed client (success) +// 5. Update session key v2 -> [YELLOW] only +// 6. Withdraw YELLOW (success); attempt YUSD withdraw via session key (expected fail) +// 7. Update session key v3 -> [YUSD] only +// 8. Withdraw YUSD (success); attempt YELLOW deposit via session key (expected fail) +// 9. Revoke session key v4 -> [] +// 10. Attempt YUSD deposit, YELLOW deposit, and channel closure via session key +// (all expected to fail) + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/shopspring/decimal" + + "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://nitronode-sandbox.yellow.org/v1/ws" + + // Replace with your hex private key. The wallet must have minimum off-chain + // balance for YUSD and YELLOW; channels are auto-opened below if missing. + walletPrivateKey := "0x7d607..." + + // chainID is the home blockchain ID used for Deposit / Withdraw calls. Set + // it to the asset's home chain on the target nitronode deployment. 11155111 + // is Ethereum Sepolia (the stress environment). + chainID := uint64(11155111) + + // rpcURL is a JSON-RPC endpoint for chainID. Replace with your own provider + // if the public endpoint is rate-limited. + rpcURL := "https://sepolia.drpc.org" + + // --- Setup wallet signers + wallet-backed SDK client --- + walletRawSigner, err := sign.NewEthereumRawSigner(walletPrivateKey) + if err != nil { + log.Fatalf("Invalid wallet private key: %v", err) + } + walletMsgSigner, err := sign.NewEthereumMsgSignerFromRaw(walletRawSigner) + if err != nil { + log.Fatalf("Failed to create wallet msg signer: %v", err) + } + walletChannelSigner, err := core.NewChannelDefaultSigner(walletMsgSigner) + if err != nil { + log.Fatalf("Failed to create wallet channel signer: %v", err) + } + walletAddress := walletRawSigner.PublicKey().Address().String() + fmt.Printf("Wallet: %s\n\n", walletAddress) + + walletClient, err := sdk.NewClient(wsURL, walletChannelSigner, walletRawSigner, sdk.WithBlockchainRPC(chainID, rpcURL)) + if err != nil { + log.Fatalf("Failed to create wallet client: %v", err) + } + defer walletClient.Close() + + // --- Step 1: ensure YUSD and YELLOW channels are open --- + fmt.Println("=== Step 1: Ensuring channels are open ===") + ensureChannelOpen(ctx, walletClient, "yusd") + ensureChannelOpen(ctx, walletClient, "yellow") + fmt.Println() + + // --- Step 2: generate a fresh session key --- + fmt.Println("=== Step 2: Generating session key ===") + sessionKeyRawSigner, sessionKeyMsgSigner := generateSessionKey() + sessionKeyAddress := sessionKeyRawSigner.PublicKey().Address().String() + fmt.Printf("Session key: %s\n\n", sessionKeyAddress) + + // --- Step 3: register session key v1 with both assets allowed --- + fmt.Println("=== Step 3: Registering session key v1 ([yusd, yellow]) ===") + stateV1 := submitSessionKey(ctx, walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 1, []string{"yusd", "yellow"}) + fmt.Println("✓ v1 registered") + fmt.Println() + + // --- Step 4: deposit YUSD and YELLOW via session-key client --- + fmt.Println("=== Step 4: Depositing via session-key client (v1) ===") + skClient1 := newSessionKeyClient(wsURL, walletRawSigner, sessionKeyMsgSigner, stateV1, sdk.WithBlockchainRPC(chainID, rpcURL)) + yusdDepositState, err := skClient1.Deposit(ctx, chainID, "yusd", decimal.NewFromFloat(0.00001)) + if err != nil { + log.Fatalf("YUSD deposit via v1 failed: %v", err) + } + fmt.Println("✓ YUSD deposited via session key") + checkpointAndWait(ctx, skClient1, "yusd", yusdDepositState.Version) + + yellowDepositState, err := skClient1.Deposit(ctx, chainID, "yellow", decimal.NewFromFloat(0.00001)) + if err != nil { + log.Fatalf("YELLOW deposit via v1 failed: %v", err) + } + fmt.Println("✓ YELLOW deposited via session key") + checkpointAndWait(ctx, skClient1, "yellow", yellowDepositState.Version) + skClient1.Close() + fmt.Println() + + // --- Step 5: update session key v2 -> [yellow] --- + fmt.Println("=== Step 5: Updating session key v2 ([yellow]) ===") + stateV2 := submitSessionKey(ctx, walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 2, []string{"yellow"}) + skClient2 := newSessionKeyClient(wsURL, walletRawSigner, sessionKeyMsgSigner, stateV2, sdk.WithBlockchainRPC(chainID, rpcURL)) + fmt.Println("✓ v2 registered") + fmt.Println() + + // --- Step 6: withdraw YELLOW (ok); attempt YUSD withdraw (fail) --- + fmt.Println("=== Step 6: Withdraw via v2 (yellow only) ===") + yellowWithdrawState, err := skClient2.Withdraw(ctx, chainID, "yellow", decimal.NewFromFloat(0.000005)) + if err != nil { + log.Fatalf("YELLOW withdraw via v2 failed: %v", err) + } + fmt.Println("✓ YELLOW withdrawn via session key") + checkpointAndWait(ctx, skClient2, "yellow", yellowWithdrawState.Version) + if _, err := skClient2.Withdraw(ctx, chainID, "yusd", decimal.NewFromFloat(0.000005)); err != nil { + fmt.Printf("✓ Expected: YUSD withdraw rejected by node: %v\n", err) + } else { + fmt.Println("✗ Unexpected: YUSD withdraw succeeded under v2") + } + skClient2.Close() + fmt.Println() + + // --- Step 7: update session key v3 -> [yusd] --- + fmt.Println("=== Step 7: Updating session key v3 ([yusd]) ===") + stateV3 := submitSessionKey(ctx, walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 3, []string{"yusd"}) + skClient3 := newSessionKeyClient(wsURL, walletRawSigner, sessionKeyMsgSigner, stateV3, sdk.WithBlockchainRPC(chainID, rpcURL)) + fmt.Println("✓ v3 registered") + fmt.Println() + + // --- Step 8: withdraw YUSD (ok); attempt YELLOW deposit (fail) --- + fmt.Println("=== Step 8: Withdraw via v3 (yusd only) ===") + yusdWithdrawState, err := skClient3.Withdraw(ctx, chainID, "yusd", decimal.NewFromFloat(0.000005)) + if err != nil { + log.Fatalf("YUSD withdraw via v3 failed: %v", err) + } + fmt.Println("✓ YUSD withdrawn via session key") + checkpointAndWait(ctx, skClient3, "yusd", yusdWithdrawState.Version) + if _, err := skClient3.Deposit(ctx, chainID, "yellow", decimal.NewFromFloat(0.000005)); err != nil { + fmt.Printf("✓ Expected: YELLOW deposit rejected by node: %v\n", err) + } else { + fmt.Println("✗ Unexpected: YELLOW deposit succeeded under v3") + } + skClient3.Close() + fmt.Println() + + // --- Step 9: revoke session key v4 -> [] --- + // Empty assets disables every per-asset check on the node, so the next + // version of the key cannot authorize any channel operation. + fmt.Println("=== Step 9: Revoking session key v4 (empty assets) ===") + stateV4 := submitSessionKey(ctx, walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 4, []string{}) + skClient4 := newSessionKeyClient(wsURL, walletRawSigner, sessionKeyMsgSigner, stateV4, sdk.WithBlockchainRPC(chainID, rpcURL)) + fmt.Println("✓ v4 registered (revoked)") + fmt.Println() + + // --- Step 10: every session-key operation must fail --- + fmt.Println("=== Step 10: Verifying revoked session key cannot operate ===") + if _, err := skClient4.Deposit(ctx, chainID, "yusd", decimal.NewFromFloat(0.000005)); err != nil { + fmt.Printf("✓ Expected: YUSD deposit rejected by node: %v\n", err) + } else { + fmt.Println("✗ Unexpected: YUSD deposit succeeded under v4") + } + if _, err := skClient4.Deposit(ctx, chainID, "yellow", decimal.NewFromFloat(0.000005)); err != nil { + fmt.Printf("✓ Expected: YELLOW deposit rejected by node: %v\n", err) + } else { + fmt.Println("✗ Unexpected: YELLOW deposit succeeded under v4") + } + if _, err := skClient4.CloseHomeChannel(ctx, "yusd"); err != nil { + fmt.Printf("✓ Expected: YUSD channel close rejected by node: %v\n", err) + } else { + fmt.Println("✗ Unexpected: YUSD channel close succeeded under v4") + } + skClient4.Close() + + fmt.Println("\n=== Example Complete ===") +} + +// ensureChannelOpen guarantees that the wallet has an acknowledged channel +// open for asset. If the node holds no state for the wallet/asset pair, or +// the latest state is still awaiting the user's signature (or has been +// finalized), Acknowledge is invoked to create or progress the channel. +func ensureChannelOpen(ctx context.Context, client *sdk.Client, asset string) { + wallet := client.GetUserAddress() + state, err := client.GetLatestState(ctx, wallet, asset, false) + if err != nil { + log.Fatalf("failed to get latest %s state: %v", asset, err) + } + + hasOpenChannel := state != nil && + state.HomeChannelID != nil && + !state.IsFinal() && + state.UserSig != nil + if hasOpenChannel { + fmt.Printf("✓ channel already open for %s\n", asset) + return + } + + if _, err := client.Acknowledge(ctx, asset); err != nil { + log.Fatalf("failed to acknowledge %s channel: %v", asset, err) + } + fmt.Printf("✓ acknowledged channel for %s\n", asset) +} + +// generateSessionKey produces a fresh keypair and returns both the raw signer +// (used as the SDK client's tx signer) and the EIP-191 msg signer (used to +// sign channel session-key authorization payloads and channel states). +func generateSessionKey() (sign.Signer, *sign.EthereumMsgSigner) { + privateKey, err := crypto.GenerateKey() + if err != nil { + log.Fatalf("failed to generate session key: %v", err) + } + privateKeyHex := hexutil.Encode(crypto.FromECDSA(privateKey)) + + rawSigner, err := sign.NewEthereumRawSigner(privateKeyHex) + if err != nil { + log.Fatalf("failed to create session key raw signer: %v", err) + } + msgSigner, err := sign.NewEthereumMsgSigner(privateKeyHex) + if err != nil { + log.Fatalf("failed to create session key msg signer: %v", err) + } + return rawSigner, msgSigner +} + +// submitSessionKey signs and submits a (version, assets) update for the +// channel session key using the wallet client. Returns the registered state +// (including UserSig + SessionKeySig) so the caller can derive the matching +// session-key channel signer for subsequent operations. +func submitSessionKey( + ctx context.Context, + walletClient *sdk.Client, + walletAddress, sessionKeyAddress string, + sessionKeyMsgSigner *sign.EthereumMsgSigner, + version uint64, + assets []string, +) core.ChannelSessionKeyStateV1 { + state := core.ChannelSessionKeyStateV1{ + UserAddress: walletAddress, + SessionKey: sessionKeyAddress, + Version: version, + Assets: assets, + ExpiresAt: time.Now().Add(24 * time.Hour), + } + + userSig, err := walletClient.SignChannelSessionKeyState(state) + if err != nil { + log.Fatalf("failed to sign session key v%d state: %v", version, err) + } + state.UserSig = userSig + + sessionKeySig, err := sdk.SignChannelSessionKeyOwnership(state, sessionKeyMsgSigner) + if err != nil { + log.Fatalf("failed to sign session key v%d ownership: %v", version, err) + } + state.SessionKeySig = sessionKeySig + + if err := walletClient.SubmitChannelSessionKeyState(ctx, state); err != nil { + log.Fatalf("failed to submit session key v%d: %v", version, err) + } + return state +} + +// newSessionKeyClient builds an SDK client whose state signer is the channel +// session key derived from the registered state. All channel state operations +// (Deposit, Withdraw, CloseHomeChannel, ...) issued through this client are +// signed with the session key, and the node validates them against the latest +// registered (user, session_key, version) tuple — including the asset +// allow-list and expiry. +// +// rawSigner must remain the wallet's raw signer: the SDK uses it to derive +// the user address (GetUserAddress) and to look up state for the correct +// owner. Substituting the session key here would point the client at the +// session key's address — channels would be queried/created for the wrong +// owner. +func newSessionKeyClient( + wsURL string, + walletRawSigner sign.Signer, + sessionKeyMsgSigner *sign.EthereumMsgSigner, + state core.ChannelSessionKeyStateV1, + opts ...sdk.Option, +) *sdk.Client { + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(state.UserAddress, state.Version, state.Assets, state.ExpiresAt.Unix()) + if err != nil { + log.Fatalf("failed to compute metadata hash for v%d: %v", state.Version, err) + } + + channelSigner, err := core.NewChannelSessionKeySignerV1(sessionKeyMsgSigner, metadataHash.Hex(), state.UserSig) + if err != nil { + log.Fatalf("failed to build session-key channel signer for v%d: %v", state.Version, err) + } + + client, err := sdk.NewClient(wsURL, channelSigner, walletRawSigner, opts...) + if err != nil { + log.Fatalf("failed to create session-key client for v%d: %v", state.Version, err) + } + return client +} + +// checkpointAndWait runs Checkpoint for asset and polls GetHomeChannel until +// the node's observed on-chain state_version catches up to expectedVersion. +// Without this barrier the next deposit/withdraw can race the node's event +// ingestion and be rejected with "home deposit is still ongoing". +func checkpointAndWait(ctx context.Context, client *sdk.Client, asset string, expectedVersion uint64) { + txHash, err := client.Checkpoint(ctx, asset) + if err != nil { + log.Fatalf("checkpoint %s failed: %v", asset, err) + } + fmt.Printf(" ↳ checkpoint %s tx %s submitted; waiting for node to observe state_version=%d...\n", asset, txHash, expectedVersion) + + wallet := client.GetUserAddress() + deadline := time.Now().Add(2 * time.Minute) + for { + channel, err := client.GetHomeChannel(ctx, wallet, asset) + if err != nil { + log.Fatalf("failed to get home channel for %s: %v", asset, err) + } + if channel != nil && channel.StateVersion >= expectedVersion { + fmt.Printf(" ↳ node observed state_version=%d for %s\n", channel.StateVersion, asset) + return + } + if time.Now().After(deadline) { + log.Fatalf("timed out waiting for %s to reach state_version=%d", asset, expectedVersion) + } + time.Sleep(2 * time.Second) + } +} diff --git a/sdk/go/examples/round_robin/main.go b/sdk/go/examples/round_robin/main.go new file mode 100644 index 000000000..768a41abd --- /dev/null +++ b/sdk/go/examples/round_robin/main.go @@ -0,0 +1,664 @@ +package main + +// Example: Round Robin Test +// +// Exercises every basic channel action (deposit, transfer, close, withdraw) +// for one asset across every chain on which that asset is supported. +// +// ---------------------------------------------------------------------------- +// Flow +// ---------------------------------------------------------------------------- +// +// 1. Preparation +// a. Set up wallet signers for private keys A and B. +// b. Build tokenSet: every supported token for the configured asset, +// paired with its chain's JSON-RPC endpoint. tokenSet[i].BlockchainID +// must have an entry in chainRPCs. +// c. Ensure signer A holds >= minNativeBalances[chainID] of the native +// gas token on every chain in tokenSet. Each chain pays three +// checkpoint transactions per run (deposit + close + withdraw). +// d. Ensure signer A holds >= transferAmount of the configured asset on +// tokenSet[0].BlockchainID. Subsequent iterations are seeded by the +// previous iteration's withdrawal — no other chain needs to be +// pre-funded with the asset. +// e. If sessionKeyPriv is non-empty, register it as a channel session +// key for signer A and use the session-key-backed client for every +// channel operation in the loop. +// +// 2. For i := range tokenSet (let next = (i + 1) % len(tokenSet)): +// a. Signer A deposits transferAmount of tokenSet[i]. +// b. Signer A transfers transferAmount to signer B (off-chain). +// c. Signer A closes the home channel for the asset. +// d. Signer B transfers transferAmount back to signer A (lands on a +// void state, no chain attached). +// e. Signer A withdraws transferAmount on tokenSet[next].BlockchainID, +// which auto-creates a new channel on that chain. On the last +// iteration this wraps to tokenSet[0], closing the loop. +// +// Together (2.a–2.e) exercise deposit / transfer-send / close / +// transfer-receive / withdraw on every chain in tokenSet. +// +// ---------------------------------------------------------------------------- +// Operational notes +// ---------------------------------------------------------------------------- +// +// - wsURL must point at a reachable nitronode WebSocket endpoint. +// - The nitronode must maintain reserves of the asset on every chain in +// tokenSet. 2.e withdraws tokens signer A never deposited on that chain, +// which only works if the node holds liquidity there. +// - tokenSet is derived at runtime from GetAssets(asset).Tokens; the +// iteration order matches the order returned by the node. +// - The example exits non-zero on the first failure. It does not reset +// state, so re-running on the same wallets resumes from whatever state +// the node holds. Fresh wallets give the cleanest preflight numbers. + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "strings" + "time" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" +) + +// ============================================================================ +// Configuration +// ============================================================================ + +const ( + wsURL = "wss://nitronode-sandbox.yellow.org/v1/ws" + + // Replace with your hex private keys. privA performs every channel + // operation; privB only receives transfers and sends them back. + privA = "0x7d6071..." + privB = "0xf63695..." + + // Empty string disables the session-key path; the wallet client performs + // channel operations directly. Otherwise this key is registered as a + // channel session key for privA and used for the loop. + sessionKeyPriv = "" + + // Asset symbol to test. tokenSet is built from this asset's tokens as + // returned by the node. + asset = "yusd" +) + +// transferAmount is the value used for every deposit / transfer / withdraw +// in the loop. Keep this small for testnets. +var transferAmount = decimal.NewFromFloat(0.00001) + +// chainRPCs maps blockchain ID -> JSON-RPC endpoint. Must cover every chain +// in tokenSet (derived at runtime). Missing entries fail preflight. +var chainRPCs = map[uint64]string{ + 11155111: "https://sepolia.drpc.org", // Ethereum Sepolia + 84532: "https://sepolia.base.org", // Base Sepolia + 80002: "https://rpc-amoy.polygon.technology", // Polygon Amoy + 59141: "https://rpc.sepolia.linea.build", // Linea Sepolia + 1449000: "https://rpc.testnet.xrplevm.org", // XRP LVM Testnet + + 1: "https://0xrpc.io/eth", // Ethereum Mainnet + 14: "https://rpc.ankr.com/flare", // Flare Mainnet + 56: "https://bsc.api.pocket.network", // BNB Smart Chain Mainnet + 137: "https://polygon-bor-rpc.publicnode.com", // Polygon Mainnet + 8453: "https://base-rpc.publicnode.com", // Base Mainnet + 59144: "https://linea.drpc.org", // Linea Mainnet + 1440000: "https://xrpl.drpc.org", // XRP EVM Mainnet +} + +// minNativeBalances maps blockchain ID -> minimum native gas balance for +// privA. Sized to cover three checkpoint transactions (deposit + close + +// withdraw). Tune per chain based on observed gas costs. +var minNativeBalances = map[uint64]decimal.Decimal{ + 11155111: decimal.NewFromFloat(0.01), + 84532: decimal.NewFromFloat(0.005), + 80002: decimal.NewFromFloat(0.05), + 59141: decimal.NewFromFloat(0.005), + 1449000: decimal.NewFromFloat(0.001), + + 1: decimal.NewFromFloat(0.001), + 14: decimal.NewFromFloat(0.001), + 56: decimal.NewFromFloat(0.001), + 137: decimal.NewFromFloat(3), + 8453: decimal.NewFromFloat(0.001), + 59144: decimal.NewFromFloat(0.001), + 1440000: decimal.NewFromFloat(0.001), +} + +// ============================================================================ +// Main +// ============================================================================ + +func main() { + ctx := context.Background() + + // --- Build signers for A and B --- + signersA := buildSigners(privA, "A") + signersB := buildSigners(privB, "B") + fmt.Printf("Wallet A: %s\nWallet B: %s\n\n", signersA.address, signersB.address) + + // --- Build wallet clients with every RPC pre-registered --- + walletA := newClient(signersA.channelSigner, signersA.rawSigner) + defer walletA.Close() + walletB := newClient(signersB.channelSigner, signersB.rawSigner) + defer walletB.Close() + + // --- 1.b: discover tokenSet for the configured asset --- + fmt.Println("=== 1.b: Discovering tokenSet ===") + tokenSet := discoverTokenSet(ctx, walletA, asset) + for i, t := range tokenSet { + fmt.Printf(" [%d] %s on chain %d (%s)\n", i, t.Symbol, t.BlockchainID, t.Address) + } + fmt.Println() + + // --- 1.c + 1.d: preflight native and asset balances --- + fmt.Println("=== 1.c + 1.d: Preflight ===") + preflight(ctx, walletA, signersA.address, tokenSet) + fmt.Println("✓ preflight passed") + fmt.Println() + + // --- 1.e: optional session-key registration --- + opsClient := walletA + if sessionKeyPriv != "" { + fmt.Println("=== 1.e: Registering channel session key ===") + opsClient = setupSessionKeyClient(ctx, walletA, signersA) + defer opsClient.Close() + fmt.Println("✓ session-key client ready") + fmt.Println() + } + + // --- 2: round-robin loop --- + fmt.Println("=== 2: Round robin ===") + for i := range tokenSet { + next := (i + 1) % len(tokenSet) + runIteration(ctx, i, opsClient, walletB, signersA.address, signersB.address, tokenSet[i], tokenSet[next]) + } + + fmt.Println("\n=== Example Complete ===") +} + +// ============================================================================ +// Iteration +// ============================================================================ + +// runIteration executes one (deposit -> transfer -> close -> transfer-back -> +// withdraw) cycle. cur is the chain on which the deposit/close happen; next +// is the chain on which the withdraw settles. +func runIteration( + ctx context.Context, + i int, + opsClient *sdk.Client, + walletB *sdk.Client, + addrA, addrB string, + cur, next core.Token, +) { + fmt.Printf("--- iter %d: deposit on chain %d, withdraw on chain %d ---\n", i, cur.BlockchainID, next.BlockchainID) + + // 2.a A deposits transferAmount on cur.BlockchainID. Wait for the approve + // tx to be mined before issuing Deposit; ApproveToken returns immediately + // after broadcast, and Deposit will revert if the allowance has not + // settled on-chain yet. + approveTx, err := opsClient.ApproveToken(ctx, cur.BlockchainID, asset, transferAmount) + if err != nil { + log.Fatalf("iter %d: approve on chain %d failed: %v", i, cur.BlockchainID, err) + } + waitForTxReceipt(ctx, chainRPCs[cur.BlockchainID], approveTx) + depositState, err := opsClient.Deposit(ctx, cur.BlockchainID, asset, transferAmount) + if err != nil { + log.Fatalf("iter %d: deposit on chain %d failed: %v", i, cur.BlockchainID, err) + } + fmt.Printf(" ✓ A deposited %s %s on chain %d\n", transferAmount, asset, cur.BlockchainID) + checkpointAndWait(ctx, opsClient, asset, depositState.Version) + + // 2.b A -> B (off-chain). + if _, err := opsClient.Transfer(ctx, addrB, asset, transferAmount); err != nil { + log.Fatalf("iter %d: transfer A->B failed: %v", i, err) + } + fmt.Printf(" ✓ A transferred %s %s to B (off-chain)\n", transferAmount, asset) + + // 2.c A closes home channel for asset on cur.BlockchainID. + if _, err := opsClient.CloseHomeChannel(ctx, asset); err != nil { + log.Fatalf("iter %d: close home channel failed: %v", i, err) + } + fmt.Printf(" ✓ A closed home channel for %s\n", asset) + closeAndWait(ctx, opsClient, asset) + + // 2.d B -> A (off-chain credit, lands on void state, no chain attached). + if _, err := walletB.Transfer(ctx, addrA, asset, transferAmount); err != nil { + log.Fatalf("iter %d: transfer B->A failed: %v", i, err) + } + fmt.Printf(" ✓ B transferred %s %s back to A (off-chain credit)\n", transferAmount, asset) + + // 2.e A withdraws on next.BlockchainID. Withdraw auto-creates a new + // channel on next.BlockchainID because A's latest state is void after + // close + transfer-receive. + withdrawState, err := opsClient.Withdraw(ctx, next.BlockchainID, asset, transferAmount) + if err != nil { + log.Fatalf("iter %d: withdraw on chain %d failed: %v", i, next.BlockchainID, err) + } + fmt.Printf(" ✓ A withdrew %s %s on chain %d\n", transferAmount, asset, next.BlockchainID) + checkpointAndWait(ctx, opsClient, asset, withdrawState.Version) + + // Wait until next chain shows the funds on-chain so the next iteration's + // deposit doesn't race ERC-20 settlement. + waitForOnChain(ctx, opsClient, next.BlockchainID, asset, addrA, transferAmount) + fmt.Println() +} + +// ============================================================================ +// Preflight +// ============================================================================ + +func preflight(ctx context.Context, walletA *sdk.Client, addrA string, tokenSet []core.Token) { + var shortfalls []string + + // Verify chainRPCs / minNativeBalances cover every chain in tokenSet. + for _, t := range tokenSet { + if _, ok := chainRPCs[t.BlockchainID]; !ok { + shortfalls = append(shortfalls, fmt.Sprintf("chainRPCs missing entry for chain %d", t.BlockchainID)) + } + if _, ok := minNativeBalances[t.BlockchainID]; !ok { + shortfalls = append(shortfalls, fmt.Sprintf("minNativeBalances missing entry for chain %d", t.BlockchainID)) + } + } + if len(shortfalls) > 0 { + fmt.Println("Configuration shortfalls:") + for _, s := range shortfalls { + fmt.Printf(" - %s\n", s) + } + os.Exit(1) + } + + // 1.c: native balance on every chain >= minNativeBalances. + fmt.Println("Native balance check:") + for _, t := range tokenSet { + have, err := nativeBalance(ctx, chainRPCs[t.BlockchainID], addrA) + if err != nil { + log.Fatalf("native balance check for chain %d failed: %v", t.BlockchainID, err) + } + need := minNativeBalances[t.BlockchainID] + marker := "✓" + if have.LessThan(need) { + marker = "✗" + shortfalls = append(shortfalls, fmt.Sprintf("chain %d native: need >= %s, have %s", t.BlockchainID, need, have)) + } + fmt.Printf(" %s chain %d: need >= %s, have %s\n", marker, t.BlockchainID, need, have) + } + + // 1.d: privA holds >= transferAmount of asset on tokenSet[0].BlockchainID. + seedChain := tokenSet[0].BlockchainID + have, err := walletA.GetOnChainBalance(ctx, seedChain, asset, addrA) + if err != nil { + log.Fatalf("asset balance check on chain %d failed: %v", seedChain, err) + } + marker := "✓" + if have.LessThan(transferAmount) { + marker = "✗" + shortfalls = append(shortfalls, fmt.Sprintf("chain %d %s: need >= %s, have %s", seedChain, asset, transferAmount, have)) + } + fmt.Printf("Asset balance check:\n %s chain %d %s: need >= %s, have %s\n", marker, seedChain, asset, transferAmount, have) + + if len(shortfalls) > 0 { + fmt.Println("\nPreflight failed. Resolve the following before re-running:") + for _, s := range shortfalls { + fmt.Printf(" - %s\n", s) + } + os.Exit(1) + } +} + +// nativeBalance dials rpcURL and returns the native token balance of addr +// converted to a decimal with 18-decimal precision (standard for EVM native). +func nativeBalance(ctx context.Context, rpcURL, addr string) (decimal.Decimal, error) { + cl, err := ethclient.DialContext(ctx, rpcURL) + if err != nil { + return decimal.Zero, fmt.Errorf("dial %s: %w", rpcURL, err) + } + defer cl.Close() + wei, err := cl.BalanceAt(ctx, common.HexToAddress(addr), nil) + if err != nil { + return decimal.Zero, err + } + return decimal.NewFromBigInt(wei, 0).Shift(-18), nil +} + +// approveConfirmations is the number of additional blocks the example waits +// for after an approve tx receipt before issuing the downstream deposit / +// checkpoint. Public RPC endpoints are often load-balanced across multiple +// nodes, and an eth_call read can hit a node that has not yet indexed the +// approve. Waiting a few blocks past the receipt gives the cluster time to +// converge on the post-tx state. +const approveConfirmations uint64 = 3 + +// waitForTxReceipt polls rpcURL until the given tx is mined and asserts a +// successful (status=1) receipt, then waits for approveConfirmations more +// blocks on top before returning. Fatals on revert or timeout. Needed because +// the SDK's ApproveToken returns immediately after broadcast, and the +// downstream Deposit / Checkpoint would otherwise race the allowance update +// on load-balanced public RPCs. +func waitForTxReceipt(ctx context.Context, rpcURL, txHash string) { + cl, err := ethclient.DialContext(ctx, rpcURL) + if err != nil { + log.Fatalf("dial %s: %v", rpcURL, err) + } + defer cl.Close() + + hash := common.HexToHash(txHash) + deadline := time.Now().Add(2 * time.Minute) + + var minedBlock uint64 + for { + receipt, err := cl.TransactionReceipt(ctx, hash) + if err == nil { + if receipt.Status != 1 { + log.Fatalf("tx %s reverted on-chain", txHash) + } + minedBlock = receipt.BlockNumber.Uint64() + break + } + if !errors.Is(err, ethereum.NotFound) { + log.Fatalf("TransactionReceipt %s: %v", txHash, err) + } + if time.Now().After(deadline) { + log.Fatalf("timed out waiting for tx %s", txHash) + } + time.Sleep(2 * time.Second) + } + + target := minedBlock + approveConfirmations + for { + head, err := cl.BlockNumber(ctx) + if err != nil { + log.Fatalf("BlockNumber on %s: %v", rpcURL, err) + } + if head >= target { + return + } + if time.Now().After(deadline) { + log.Fatalf("timed out waiting for %d confirmations on tx %s (head=%d, target=%d)", approveConfirmations, txHash, head, target) + } + time.Sleep(2 * time.Second) + } +} + +// ============================================================================ +// Token discovery +// ============================================================================ + +func discoverTokenSet(ctx context.Context, client *sdk.Client, symbol string) []core.Token { + assets, err := client.GetAssets(ctx, nil) + if err != nil { + log.Fatalf("GetAssets failed: %v", err) + } + for _, a := range assets { + if strings.EqualFold(a.Symbol, symbol) { + if len(a.Tokens) == 0 { + log.Fatalf("asset %s has no supported tokens", symbol) + } + return a.Tokens + } + } + log.Fatalf("asset %s not supported by node", symbol) + return nil +} + +// ============================================================================ +// Client construction +// ============================================================================ + +type signers struct { + address string + rawSigner sign.Signer + msgSigner *sign.EthereumMsgSigner + channelSigner core.ChannelSigner +} + +func buildSigners(privateKeyHex, label string) signers { + raw, err := sign.NewEthereumRawSigner(privateKeyHex) + if err != nil { + log.Fatalf("invalid %s private key: %v", label, err) + } + msg, err := sign.NewEthereumMsgSignerFromRaw(raw) + if err != nil { + log.Fatalf("failed to build %s msg signer: %v", label, err) + } + ch, err := core.NewChannelDefaultSigner(msg) + if err != nil { + log.Fatalf("failed to build %s channel signer: %v", label, err) + } + return signers{ + address: raw.PublicKey().Address().String(), + rawSigner: raw, + msgSigner: msg, + channelSigner: ch, + } +} + +// newClient builds an SDK client with every configured chain RPC attached. +func newClient(channelSigner core.ChannelSigner, rawSigner sign.Signer) *sdk.Client { + opts := make([]sdk.Option, 0, len(chainRPCs)) + for chainID, url := range chainRPCs { + opts = append(opts, sdk.WithBlockchainRPC(chainID, url)) + } + client, err := sdk.NewClient(wsURL, channelSigner, rawSigner, opts...) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + return client +} + +// ============================================================================ +// Session key +// ============================================================================ + +func setupSessionKeyClient(ctx context.Context, walletA *sdk.Client, sa signers) *sdk.Client { + var skRaw sign.Signer + var skMsg *sign.EthereumMsgSigner + if sessionKeyPriv == "" { + skRaw, skMsg = generateSessionKey() + } else { + var err error + skRaw, err = sign.NewEthereumRawSigner(sessionKeyPriv) + if err != nil { + log.Fatalf("invalid sessionKeyPriv: %v", err) + } + skMsg, err = sign.NewEthereumMsgSigner(sessionKeyPriv) + if err != nil { + log.Fatalf("session-key msg signer: %v", err) + } + } + skAddress := skRaw.PublicKey().Address().String() + fmt.Printf("Session key: %s\n", skAddress) + + // If a session-key state already exists for this address (active or + // expired/revoked), the protocol requires the next submission to use a + // strictly higher monotonic version. Look up the latest stored version + // and start from version+1; otherwise begin at 1. + includeInactive := true + prior, err := walletA.GetLastChannelKeyStates(ctx, sa.address, &sdk.GetLastChannelKeyStatesOptions{ + SessionKey: &skAddress, + IncludeInactive: &includeInactive, + }) + if err != nil { + log.Fatalf("lookup prior session key states: %v", err) + } + version := uint64(1) + for _, p := range prior { + if p.Version >= version { + version = p.Version + 1 + } + } + if version > 1 { + fmt.Printf(" ↳ existing state found at version %d, registering v%d\n", version-1, version) + } + + state := core.ChannelSessionKeyStateV1{ + UserAddress: sa.address, + SessionKey: skAddress, + Version: version, + Assets: []string{asset}, + ExpiresAt: time.Now().Add(24 * time.Hour), + } + + userSig, err := walletA.SignChannelSessionKeyState(state) + if err != nil { + log.Fatalf("sign session key state: %v", err) + } + state.UserSig = userSig + + skSig, err := sdk.SignChannelSessionKeyOwnership(state, skMsg) + if err != nil { + log.Fatalf("sign session key ownership: %v", err) + } + state.SessionKeySig = skSig + + if err := walletA.SubmitChannelSessionKeyState(ctx, state); err != nil { + log.Fatalf("submit session key state: %v", err) + } + + metadataHash, err := core.GetChannelSessionKeyAuthMetadataHashV1(state.UserAddress, state.Version, state.Assets, state.ExpiresAt.Unix()) + if err != nil { + log.Fatalf("compute metadata hash: %v", err) + } + skChannelSigner, err := core.NewChannelSessionKeySignerV1(skMsg, metadataHash.Hex(), state.UserSig) + if err != nil { + log.Fatalf("build session-key channel signer: %v", err) + } + + // rawSigner stays as the wallet's key: the SDK uses it to derive the + // user address and to sign on-chain checkpoint transactions. + opts := make([]sdk.Option, 0, len(chainRPCs)) + for chainID, url := range chainRPCs { + opts = append(opts, sdk.WithBlockchainRPC(chainID, url)) + } + client, err := sdk.NewClient(wsURL, skChannelSigner, sa.rawSigner, opts...) + if err != nil { + log.Fatalf("build session-key client: %v", err) + } + return client +} + +func generateSessionKey() (sign.Signer, *sign.EthereumMsgSigner) { + priv, err := crypto.GenerateKey() + if err != nil { + log.Fatalf("generate session key: %v", err) + } + privHex := hexutil.Encode(crypto.FromECDSA(priv)) + raw, err := sign.NewEthereumRawSigner(privHex) + if err != nil { + log.Fatalf("session-key raw signer: %v", err) + } + msg, err := sign.NewEthereumMsgSigner(privHex) + if err != nil { + log.Fatalf("session-key msg signer: %v", err) + } + return raw, msg +} + +// ============================================================================ +// Wait helpers +// ============================================================================ + +// closeAndWait runs Checkpoint after a Finalize transition and polls +// GetHomeChannel until the node observes the on-chain close. Closure is +// signalled either by the home-channel row dropping out (nil) or by its +// status being reset to Void. +func closeAndWait(ctx context.Context, client *sdk.Client, asset string) { + txHash, err := client.Checkpoint(ctx, asset) + if err != nil { + log.Fatalf("checkpoint %s (close) failed: %v", asset, err) + } + fmt.Printf(" ↳ checkpoint %s tx %s; waiting for channel close (nil or status=Void)...\n", asset, txHash) + + wallet := client.GetUserAddress() + deadline := time.Now().Add(2 * time.Minute) + for { + channel, err := client.GetHomeChannel(ctx, wallet, asset) + if err != nil { + log.Fatalf("GetHomeChannel %s: %v", asset, err) + } + if channel == nil || channel.Status == core.ChannelStatusVoid { + return + } + if time.Now().After(deadline) { + log.Fatalf("timed out waiting for %s channel to close (last status=%s)", asset, channel.Status) + } + time.Sleep(2 * time.Second) + } +} + +// checkpointAndWait runs Checkpoint and polls GetHomeChannel until the node +// observes the expected post-checkpoint state. +// +// When expectedVersion > 0 the helper waits for channel.StateVersion to catch +// up to expectedVersion. When expectedVersion == 0 — which happens for the +// channel-creation transitions issued by Deposit / Withdraw on a void state — +// the state_version stays at 0 even after the checkpoint, so the helper +// instead waits for channel.Status == Open. +func checkpointAndWait(ctx context.Context, client *sdk.Client, asset string, expectedVersion uint64) { + txHash, err := client.Checkpoint(ctx, asset) + if err != nil { + log.Fatalf("checkpoint %s failed: %v", asset, err) + } + if expectedVersion == 0 { + fmt.Printf(" ↳ checkpoint %s tx %s; waiting for channel status=Open...\n", asset, txHash) + } else { + fmt.Printf(" ↳ checkpoint %s tx %s; waiting for state_version=%d...\n", asset, txHash, expectedVersion) + } + + wallet := client.GetUserAddress() + deadline := time.Now().Add(2 * time.Minute) + for { + channel, err := client.GetHomeChannel(ctx, wallet, asset) + if err != nil { + log.Fatalf("GetHomeChannel %s: %v", asset, err) + } + if channel != nil { + if expectedVersion == 0 && channel.Status == core.ChannelStatusOpen { + return + } + if expectedVersion > 0 && channel.StateVersion >= expectedVersion { + return + } + } + if time.Now().After(deadline) { + if expectedVersion == 0 { + log.Fatalf("timed out waiting for %s channel to reach status=Open", asset) + } + log.Fatalf("timed out waiting for %s to reach state_version=%d", asset, expectedVersion) + } + time.Sleep(2 * time.Second) + } +} + +// waitForOnChain polls until addr's ERC-20 balance of asset on chainID is at +// least minAmount. Needed between iterations because Withdraw's on-chain +// settlement can lag the node's state_version bump. +func waitForOnChain(ctx context.Context, client *sdk.Client, chainID uint64, asset, addr string, minAmount decimal.Decimal) { + deadline := time.Now().Add(2 * time.Minute) + for { + have, err := client.GetOnChainBalance(ctx, chainID, asset, addr) + if err != nil { + log.Fatalf("GetOnChainBalance chain %d: %v", chainID, err) + } + if have.GreaterThanOrEqual(minAmount) { + fmt.Printf(" ↳ on-chain balance on chain %d settled: %s %s\n", chainID, have, asset) + return + } + if time.Now().After(deadline) { + log.Fatalf("timed out waiting for chain %d %s balance >= %s (have %s)", chainID, asset, minAmount, have) + } + time.Sleep(2 * time.Second) + } +} diff --git a/sdk/go/examples/validator_watcher/main.go b/sdk/go/examples/validator_watcher/main.go new file mode 100644 index 000000000..dae917592 --- /dev/null +++ b/sdk/go/examples/validator_watcher/main.go @@ -0,0 +1,133 @@ +package main + +// Validator Registration Monitor +// +// This example shows how to watch for ValidatorRegistered events on the ChannelHub +// contract. App builders should run this monitoring loop and alert users whenever an +// unexpected validator is registered — users then have a 1-day window +// (VALIDATOR_ACTIVATION_DELAY) to revoke their ERC20 approvals before the validator +// becomes usable. See contracts/SECURITY.md for the full security context. +// +// The RPC URL must be a WebSocket endpoint (wss://) because event subscriptions +// require a persistent connection. HTTP endpoints are not supported. +// +// Gap-free monitoring: each event carries a BlockNumber. On reconnect the example +// passes lastBlock+1 as fromBlock so any events emitted during the outage are +// replayed before live events resume — the 1-day safety window is preserved even +// across network interruptions. + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + "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, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + nitronodeURL := "wss://nitronode-sandbox.yellow.org/v1/ws" + // WebSocket RPC is required for event subscriptions. + wsRPCURL := "wss://sepolia.drpc.org" + chainID := uint64(11155111) + + // Load private key from environment to avoid accidental exposure in error messages. + privateKeyHex := os.Getenv("PRIVATE_KEY") + if privateKeyHex == "" { + log.Fatal("PRIVATE_KEY env var not set") + } + + stateSigner, err := sign.NewEthereumMsgSigner(privateKeyHex) + if err != nil { + log.Fatalf("failed to create state signer: %v", err) + } + channelSigner, err := core.NewChannelDefaultSigner(stateSigner) + if err != nil { + log.Fatalf("failed to create channel signer: %v", err) + } + txSigner, err := sign.NewEthereumRawSigner(privateKeyHex) + if err != nil { + log.Fatalf("failed to create tx signer: %v", err) + } + + client, err := sdk.NewClient( + nitronodeURL, + channelSigner, + txSigner, + sdk.WithBlockchainRPC(chainID, wsRPCURL), + ) + if err != nil { + log.Fatalf("failed to create SDK client: %v", err) + } + defer client.Close() + + fmt.Printf("Monitoring ValidatorRegistered events on chain %d...\n", chainID) + fmt.Println("Press Ctrl+C to stop.") + + // fromBlock tracks where to resume on reconnect. Zero on the first call means + // "skip historical replay and start from the current chain head". Subsequent + // reconnects pass lastBlock+1 to replay any events emitted during the outage. + // + // Limitation: if the subscription dies before any ValidatorRegistered event + // has been received, fromBlock stays 0 and the reconnect again skips history — + // events emitted during that outage window will be missed. + // + // For production use, initialise fromBlock to the ChannelHub contract's + // deployment block (or the last block number persisted in your own store) + // so every reconnect replays from a known-good anchor point. + var fromBlock uint64 + + for ctx.Err() == nil { + events, err := client.WatchValidatorRegistered(ctx, chainID, fromBlock) + if err != nil { + log.Printf("failed to start validator watcher (fromBlock=%d): %v — retrying in 5s", fromBlock, err) + select { + case <-ctx.Done(): + return + case <-time.After(5 * time.Second): + continue + } + } + + for ev := range events { + handleValidatorRegistered(ev) + // Advance fromBlock so the next reconnect replays from the block after this event. + fromBlock = ev.BlockNumber + 1 + } + + if ctx.Err() != nil { + break + } + log.Printf("Validator watcher subscription lost (will resume from block %d) — resubscribing in 5s", fromBlock) + select { + case <-ctx.Done(): + case <-time.After(5 * time.Second): + } + } + + fmt.Println("Validator watcher stopped.") +} + +// handleValidatorRegistered is called for every ValidatorRegistered event, +// including legitimate first-time registrations during normal node operation. +// This example alerts unconditionally; production code should compare the +// incoming validator ID and address against a set of expected validators +// (communicated through official Nitrolite channels) and only alert when +// an unknown validator appears. +func handleValidatorRegistered(ev *core.ValidatorRegisteredEvent) { + fmt.Printf( + "[ALERT] New validator registered on chain %d at block %d: ID=%d address=%s\n", + ev.BlockchainID, ev.BlockNumber, ev.ValidatorID, ev.Validator, + ) + fmt.Println("Verify this validator is expected via official Nitrolite channels.") + fmt.Println("If unexpected, revoke all ERC20 approvals to the ChannelHub contract immediately.") + fmt.Println("You have 1 day (VALIDATOR_ACTIVATION_DELAY) before the validator becomes active.") +} diff --git a/sdk/go/node.go b/sdk/go/node.go index 3f199eaf4..0e87fc50e 100644 --- a/sdk/go/node.go +++ b/sdk/go/node.go @@ -13,7 +13,7 @@ import ( // Node Information Methods // ============================================================================ -// Ping checks connectivity to the clearnode server. +// Ping checks connectivity to the nitronode server. // This is useful for health checks and verifying the connection is active. // // Example: @@ -28,7 +28,7 @@ func (c *Client) Ping(ctx context.Context) error { return nil } -// GetConfig retrieves the clearnode configuration including node identity and supported blockchains. +// GetConfig retrieves the nitronode configuration including node identity and supported blockchains. // // Returns: // - NodeConfig containing the node address, version, and list of supported blockchain networks diff --git a/sdk/go/utils.go b/sdk/go/utils.go index 50040fb5b..c339dbae5 100644 --- a/sdk/go/utils.go +++ b/sdk/go/utils.go @@ -93,9 +93,14 @@ func transformBalances(balances []rpc.BalanceEntryV1) ([]core.BalanceEntry, erro if err != nil { return nil, fmt.Errorf("failed to parse balance amount: %w", err) } + enforced, err := decimal.NewFromString(balance.Enforced) + if err != nil { + return nil, fmt.Errorf("failed to parse enforced amount: %w", err) + } result = append(result, core.BalanceEntry{ - Asset: balance.Asset, - Balance: amount, + Asset: balance.Asset, + Balance: amount, + Enforced: enforced, }) } return result, nil @@ -125,6 +130,8 @@ func transformChannel(channel rpc.ChannelV1) (core.Channel, error) { channelStatus = core.ChannelStatusOpen case "challenged": channelStatus = core.ChannelStatusChallenged + case "closing": + channelStatus = core.ChannelStatusClosing case "closed": channelStatus = core.ChannelStatusClosed } @@ -508,12 +515,13 @@ func transformSignedAppStateUpdateToRPC(signed app.SignedAppStateUpdateV1) rpc.S // transformChannelSessionKeyStateToRPC converts core.ChannelSessionKeyStateV1 to RPC ChannelSessionKeyStateV1. func transformChannelSessionKeyStateToRPC(state core.ChannelSessionKeyStateV1) rpc.ChannelSessionKeyStateV1 { return rpc.ChannelSessionKeyStateV1{ - UserAddress: state.UserAddress, - SessionKey: state.SessionKey, - Version: strconv.FormatUint(state.Version, 10), - Assets: state.Assets, - ExpiresAt: strconv.FormatInt(state.ExpiresAt.Unix(), 10), - UserSig: state.UserSig, + UserAddress: state.UserAddress, + SessionKey: state.SessionKey, + Version: strconv.FormatUint(state.Version, 10), + Assets: state.Assets, + ExpiresAt: strconv.FormatInt(state.ExpiresAt.Unix(), 10), + UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, } } @@ -535,12 +543,13 @@ func transformChannelSessionKeyState(state rpc.ChannelSessionKeyStateV1) (core.C } return core.ChannelSessionKeyStateV1{ - UserAddress: strings.ToLower(state.UserAddress), - SessionKey: strings.ToLower(state.SessionKey), - Version: version, - Assets: assets, - ExpiresAt: time.Unix(expiresAtUnix, 0), - UserSig: state.UserSig, + UserAddress: strings.ToLower(state.UserAddress), + SessionKey: strings.ToLower(state.SessionKey), + Version: version, + Assets: assets, + ExpiresAt: time.Unix(expiresAtUnix, 0), + UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, }, nil } @@ -571,6 +580,7 @@ func transformSessionKeyStateToRPC(state app.AppSessionKeyStateV1) rpc.AppSessio AppSessionIDs: state.AppSessionIDs, ExpiresAt: strconv.FormatInt(state.ExpiresAt.Unix(), 10), UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, } } @@ -604,6 +614,7 @@ func transformSessionKeyState(state rpc.AppSessionKeyStateV1) (app.AppSessionKey AppSessionIDs: appSessionIDs, ExpiresAt: time.Unix(expiresAtUnix, 0), UserSig: state.UserSig, + SessionKeySig: state.SessionKeySig, }, nil } diff --git a/sdk/go/utils_test.go b/sdk/go/utils_test.go index 71d8a0a96..4e88ab36d 100644 --- a/sdk/go/utils_test.go +++ b/sdk/go/utils_test.go @@ -84,8 +84,9 @@ func TestTransformAssets(t *testing.T) { func TestTransformBalances(t *testing.T) { rpcBalances := []rpc.BalanceEntryV1{ { - Asset: "USDC", - Amount: "100.50", + Asset: "USDC", + Amount: "100.50", + Enforced: "80.25", }, } diff --git a/sdk/go/validator_watcher.go b/sdk/go/validator_watcher.go new file mode 100644 index 000000000..3deac82b4 --- /dev/null +++ b/sdk/go/validator_watcher.go @@ -0,0 +1,71 @@ +package sdk + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/layer-3/nitrolite/pkg/blockchain/evm" + "github.com/layer-3/nitrolite/pkg/core" +) + +// WatchValidatorRegistered subscribes to ValidatorRegistered events on the ChannelHub +// contract for the given chain and delivers them on the returned channel. +// +// Each event carries the newly registered validator ID, its contract address, and the +// block number at which it was emitted. App builders should alert users and prompt them +// to revoke ERC20 approvals granted to ChannelHub whenever an unexpected validator +// appears — see contracts/SECURITY.md. +// +// Gap-free monitoring: pass fromBlock = 0 on the first call. On reconnect, pass +// lastEvent.BlockNumber + 1 so any events emitted during the outage are replayed +// before live events resume. This ensures the 1-day VALIDATOR_ACTIVATION_DELAY +// window is not silently shortened by network interruptions. +// +// The channel is closed when ctx is cancelled or the subscription is lost. On a +// lost subscription (network drop), call WatchValidatorRegistered again with the +// last received BlockNumber + 1 to resubscribe without gaps. +// +// The RPC URL configured via WithBlockchainRPC for chainID must be a WebSocket +// endpoint (wss:// or ws://). HTTP endpoints do not support event subscriptions +// and will return an error. +func (c *Client) WatchValidatorRegistered(ctx context.Context, chainID uint64, fromBlock uint64) (<-chan *core.ValidatorRegisteredEvent, error) { + rpcURL, exists := c.config.BlockchainRPCs[chainID] + if !exists { + return nil, fmt.Errorf("blockchain RPC not configured for chain %d (use WithBlockchainRPC)", chainID) + } + + channelHubAddress, err := c.getChannelHubAddress(ctx, chainID) + if err != nil { + return nil, fmt.Errorf("failed to get ChannelHub address for chain %d: %w", chainID, err) + } + + ethCl, err := ethclient.Dial(rpcURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to blockchain RPC for chain %d: %w", chainID, err) + } + + rawCh, err := evm.WatchValidatorRegistered(ctx, common.HexToAddress(channelHubAddress), ethCl, chainID, fromBlock) + if err != nil { + ethCl.Close() + return nil, err + } + + // Proxy events to the caller and close the ethClient once the subscription ends. + outCh := make(chan *core.ValidatorRegisteredEvent, 16) + go func() { + defer ethCl.Close() + defer close(outCh) + for ev := range rawCh { + select { + case outCh <- ev: + case <-ctx.Done(): + return + } + } + }() + + return outCh, nil +} diff --git a/sdk/mcp/.gitignore b/sdk/mcp/.gitignore new file mode 100644 index 000000000..eef6eaea8 --- /dev/null +++ b/sdk/mcp/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +content/ diff --git a/sdk/mcp/README.md b/sdk/mcp/README.md new file mode 100644 index 000000000..c60ff9cdf --- /dev/null +++ b/sdk/mcp/README.md @@ -0,0 +1,238 @@ +# Yellow SDK MCP Server + +An MCP (Model Context Protocol) server that exposes the **Yellow SDK / Nitrolite SDK** knowledge base to AI coding tools — covering both the **TypeScript SDK** (`@yellow-org/sdk`) and the **Go SDK** (`github.com/layer-3/nitrolite/sdk/go`). + +The npm package ships with a release-time content snapshot, so external users do **not** need to clone the Nitrolite repository. When running from this monorepo, the server reads local source files first and falls back to the packaged snapshot only when source files are unavailable. + +> **Renamed in v1.3.0** — the off-chain broker was renamed from `clearnode` to `nitronode`. v1.2.0 and earlier ship as `clearnode`; v1.3.0 and later ship as `nitronode`. See [`MIGRATION-NITRONODE.md`](../../MIGRATION-NITRONODE.md) (also exposed as the `nitrolite://migration/nitronode` MCP resource). + +## Quick Start + +Choose the client you use. All published-package examples run the server from npm, so no Nitrolite repo clone is required. + +### Claude Code + +For your current project: + +```bash +claude mcp add --transport stdio nitrolite -- npx -y @yellow-org/sdk-mcp@^1 +``` + +For a shareable project config, add this to `.mcp.json` at the repository root: + +```json +{ + "mcpServers": { + "nitrolite": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@yellow-org/sdk-mcp@^1"] + } + } +} +``` + +### Claude Desktop + +Add this to `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, or `%APPDATA%\Claude\claude_desktop_config.json` on Windows, then restart Claude Desktop: + +```json +{ + "mcpServers": { + "nitrolite": { + "command": "npx", + "args": ["-y", "@yellow-org/sdk-mcp@^1"] + } + } +} +``` + +### Codex + +```bash +codex mcp add nitrolite -- npx -y @yellow-org/sdk-mcp@^1 +``` + +### Cursor + +Add this to `~/.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "nitrolite": { + "command": "npx", + "args": ["-y", "@yellow-org/sdk-mcp@^1"] + } + } +} +``` + +### VS Code + +Add this to `.vscode/mcp.json` in your workspace, or to your VS Code user MCP config: + +```json +{ + "servers": { + "nitrolite": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@yellow-org/sdk-mcp@^1"] + } + } +} +``` + +You can also install it from the VS Code command line: + +```bash +code --add-mcp '{"name":"nitrolite","type":"stdio","command":"npx","args":["-y","@yellow-org/sdk-mcp@^1"]}' +``` + +### Local Development + +From this repository: + +```bash +cd sdk/mcp && npm install && cd ../.. +``` + +Add this to `.mcp.json`: + +```json +{ + "mcpServers": { + "nitrolite": { + "type": "stdio", + "command": "npm", + "args": ["--prefix", "sdk/mcp", "exec", "--", "tsx", "sdk/mcp/src/index.ts"] + } + } +} +``` + +Any MCP-compatible client that supports stdio servers can launch the package with the same `npx -y @yellow-org/sdk-mcp@^1` command. + +## What's Inside + +- **31 resources** — API reference (TS + Go), protocol docs, security patterns, examples (TS + Go), use cases, migration +- **9 tools** — server info, method lookup, type lookup, search, RPC format, import validation, concept explanation, scaffolding (TS + Go) +- **3 prompts** — guided workflows for building apps (covers both TS and Go), migrating from v0.5.3, building AI agents + +## Tools + +### `server_info` + +Return package, SDK, compat, Go module, protocol, and content mode metadata. Use this in bug reports. + +```text +> server_info() +``` + +### `lookup_method` + +Look up a Client method by name. Supports an optional `language` parameter. + +```text +> lookup_method({ name: "transfer" }) # TypeScript (default) +> lookup_method({ name: "Transfer", language: "go" }) # Go SDK +> lookup_method({ name: "transfer", language: "both" }) # Both SDKs +``` + +### `lookup_type` + +Look up a type, interface, struct, or enum by name. + +```text +> lookup_type({ name: "AppDefinitionV1" }) # TypeScript (default) +> lookup_type({ name: "AppSessionV1", language: "go" }) # Go (from pkg/app) +> lookup_type({ name: "ChannelStatus", language: "go" }) # Go enum with values +``` + +### `search_api` + +Fuzzy search across methods and types. + +```text +> search_api({ query: "transfer" }) # TypeScript (default) +> search_api({ query: "session", language: "go" }) # Go methods + types +> search_api({ query: "channel", language: "both" }) # Both SDKs +``` + +### `scaffold_project` + +Generate a starter project. TypeScript templates output `package.json` + `tsconfig.json` + `src/index.ts`. Go templates output `go.mod` + `main.go`. + +| Template | Language | Description | +|----------|----------|-------------| +| `transfer-app` | TypeScript | Deposit, transfer, close channel | +| `app-session` | TypeScript | Multi-party app session | +| `ai-agent` | TypeScript | Autonomous payment agent | +| `go-transfer-app` | Go | Deposit, transfer, close channel | +| `go-app-session` | Go | Multi-party app session | +| `go-ai-agent` | Go | Autonomous payment agent with graceful shutdown | + +### Other Tools + +- `get_rpc_method` — 0.5.x compat method to v1 wire format +- `validate_import` — check if a symbol is in `@yellow-org/sdk-compat` or `@yellow-org/sdk` +- `explain_concept` — plain-English explanation of protocol concepts +- `lookup_rpc_method` — full v1 RPC method lookup from `docs/api.yaml` + +--- + +## Publishing + +The package is designed for npm distribution: + +```bash +cd sdk/mcp +npm ci +npm run build +npm pack --dry-run +npm publish --access public +``` + +`npm run build` copies the release source/docs snapshot into `content/`, writes `content/release.json` and `content/manifest.json`, compiles to `dist/`, and marks the binary executable. The npm tarball includes only `dist`, `content`, `README.md`, `package.json`, and `server.json`. + +Release tags matching `mcp-v*` run `.github/workflows/publish-sdk-mcp.yml`. The workflow validates the content manifest, smokes the packed server from a clean install, publishes the npm package, and then publishes `server.json` to the MCP Registry from a separate retryable job. + +Version policy: the MCP package mirrors the SDK release it documents. If `@yellow-org/sdk` is `1.2.1`, publish `@yellow-org/sdk-mcp@1.2.1` only after `@yellow-org/sdk@1.2.1`, `@yellow-org/sdk-compat@1.2.1`, and the Go module tag `v1.2.1` are available. The server never fetches the latest GitHub release at runtime; scaffolds and `server_info` use the packaged release metadata. Consumers can use `@^1` to track compatible v1 SDK docs, or pin an exact MCP version for audited builds. + +--- + +## Resources + +### TypeScript SDK +- `nitrolite://api/methods` — TS client methods by category +- `nitrolite://api/types` — TS interfaces and type aliases +- `nitrolite://api/enums` — TS enums + +### Go SDK +- `nitrolite://go-api/methods` — Go client methods by category +- `nitrolite://go-api/types` — Go structs and enum types (from `pkg/` and `sdk/go/`) + +### Examples +- `nitrolite://examples/full-transfer-script` — complete TypeScript transfer script +- `nitrolite://examples/full-app-session-script` — complete TypeScript app session script +- `nitrolite://go-examples/full-transfer-script` — complete Go transfer script +- `nitrolite://go-examples/full-app-session-script` — complete Go app session script + +### Protocol & Security +- `nitrolite://protocol/{overview,terminology,cryptography,wire-format,rpc-methods,auth-flow,channel-lifecycle,state-model,enforcement,cross-chain,interactions}` +- `nitrolite://security/{overview,app-session-patterns,state-invariants}` + +### Use Cases +- `nitrolite://use-cases`, `nitrolite://use-cases/ai-agents` + +### Migration +- `nitrolite://migration/overview` + +--- + +## Prompts + +- `create-channel-app` — step-by-step guide covering both TypeScript and Go SDKs +- `migrate-from-v053` — migration guide from `@layer-3/nitrolite` v0.5.3 to the compat layer +- `build-ai-agent-app` — AI agent payments guide for both TypeScript and Go diff --git a/sdk/mcp/package-lock.json b/sdk/mcp/package-lock.json new file mode 100644 index 000000000..9988298d8 --- /dev/null +++ b/sdk/mcp/package-lock.json @@ -0,0 +1,1724 @@ +{ + "name": "@yellow-org/sdk-mcp", + "version": "1.3.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@yellow-org/sdk-mcp", + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "zod": "^4.4.3" + }, + "bin": { + "yellow-sdk-mcp": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/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/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/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/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/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/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/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/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-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/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "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==", + "license": "MIT" + }, + "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/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "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/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-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-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/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/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/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "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/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "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/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "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/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "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/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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "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/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-to-regexp": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", + "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "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/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "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/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/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/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/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/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/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "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/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/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/sdk/mcp/package.json b/sdk/mcp/package.json new file mode 100644 index 000000000..eb5d90a18 --- /dev/null +++ b/sdk/mcp/package.json @@ -0,0 +1,61 @@ +{ + "name": "@yellow-org/sdk-mcp", + "version": "1.3.0", + "description": "Unified MCP server for Yellow SDK and Nitrolite protocol context for AI agents and IDEs", + "type": "module", + "mcpName": "io.github.layer-3/yellow-sdk-mcp", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "yellow-sdk-mcp": "dist/index.js" + }, + "files": [ + "dist", + "content", + "README.md", + "server.json" + ], + "scripts": { + "start": "tsx src/index.ts", + "prepare-content": "node scripts/prepare-package-content.mjs", + "build": "npm run prepare-content && tsc && node scripts/set-executable.mjs dist/index.js", + "typecheck": "tsc --noEmit", + "verify:package": "node scripts/verify-package-content.mjs", + "verify:release-preflight": "node scripts/verify-release-preflight.mjs", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "zod": "^4.4.3" + }, + "overrides": { + "ip-address": "^10.2.0" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/layer-3/nitrolite.git", + "directory": "sdk/mcp" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "yellow", + "yellow-org", + "nitrolite", + "state-channels", + "sdk" + ], + "author": "Nitro Team", + "license": "MIT" +} diff --git a/sdk/mcp/scripts/prepare-package-content.mjs b/sdk/mcp/scripts/prepare-package-content.mjs new file mode 100644 index 000000000..0ce4fcb48 --- /dev/null +++ b/sdk/mcp/scripts/prepare-package-content.mjs @@ -0,0 +1,200 @@ +#!/usr/bin/env node +import { createHash } from 'node:crypto'; +import { execFileSync } from 'node:child_process'; +import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; +import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = resolve(scriptDir, '..'); +const repoRoot = resolve(packageRoot, '../..'); +const contentRoot = join(packageRoot, 'content'); +const goModulePath = 'github.com/layer-3/nitrolite'; + +const copySpecs = [ + { from: 'docs/api.yaml', to: 'docs/api.yaml', required: true }, + { from: 'docs/protocol', to: 'docs/protocol', extensions: ['.md'], required: true }, + { from: 'sdk/ts/package.json', to: 'sdk/ts/package.json', required: true }, + { from: 'sdk/ts/src', to: 'sdk/ts/src', extensions: ['.ts'], required: true }, + { from: 'sdk/ts-compat/package.json', to: 'sdk/ts-compat/package.json', required: true }, + { from: 'sdk/ts-compat/src', to: 'sdk/ts-compat/src', extensions: ['.ts'], required: true }, + { from: 'sdk/ts-compat/docs', to: 'sdk/ts-compat/docs', extensions: ['.md'], required: true }, + { from: 'sdk/go', to: 'sdk/go', extensions: ['.go', '.md'], required: true }, + { from: 'pkg/app', to: 'pkg/app', extensions: ['.go'], required: true }, + { from: 'pkg/core', to: 'pkg/core', extensions: ['.go', '.md'], required: true }, + { from: 'pkg/rpc', to: 'pkg/rpc', extensions: ['.go', '.md'], required: true }, +]; + +function toPosixPath(path) { + return path.split(sep).join('/'); +} + +function readJson(path) { + return JSON.parse(readFileSync(path, 'utf-8')); +} + +function assertWithin(root, path) { + const rel = relative(root, path); + if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) return; + throw new Error(`Refusing to write outside ${relative(repoRoot, root)}: ${path}`); +} + +function getSourceCommit() { + if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA; + try { + return execFileSync('git', ['rev-parse', 'HEAD'], { + cwd: repoRoot, + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + } catch { + return 'unknown'; + } +} + +function shouldCopy(filePath, extensions) { + if (filePath.endsWith('_test.go')) return false; + if (filePath.endsWith('.test.ts') || filePath.endsWith('.spec.ts')) return false; + if (!extensions) return true; + return extensions.some((extension) => filePath.endsWith(extension)); +} + +function copyFilteredDirectory(sourceDir, targetDir, extensions) { + let copied = 0; + for (const entry of readdirSync(sourceDir).sort()) { + if (entry === 'node_modules' || entry === 'dist') continue; + const sourcePath = join(sourceDir, entry); + const targetPath = join(targetDir, entry); + assertWithin(contentRoot, targetPath); + const linkStats = lstatSync(sourcePath); + if (linkStats.isSymbolicLink()) { + throw new Error(`Refusing to package symlink: ${relative(repoRoot, sourcePath)}`); + } + const stats = statSync(sourcePath); + if (stats.isDirectory()) { + copied += copyFilteredDirectory(sourcePath, targetPath, extensions); + continue; + } + if (!stats.isFile() || !shouldCopy(sourcePath, extensions)) continue; + mkdirSync(dirname(targetPath), { recursive: true }); + cpSync(sourcePath, targetPath); + copied += 1; + } + return copied; +} + +function collectContentFiles(root) { + const files = []; + function walk(dir) { + for (const entry of readdirSync(dir).sort()) { + const path = join(dir, entry); + const stats = statSync(path); + if (stats.isDirectory()) { + walk(path); + continue; + } + if (!stats.isFile()) continue; + const bytes = readFileSync(path); + files.push({ + path: toPosixPath(relative(root, path)), + size: stats.size, + sha256: createHash('sha256').update(bytes).digest('hex'), + }); + } + } + walk(root); + return files; +} + +function countByPrefix(files) { + const counts = {}; + for (const file of files) { + const [first, second] = file.path.split('/'); + const prefix = second ? `${first}/${second}` : first; + counts[prefix] = (counts[prefix] ?? 0) + 1; + } + return counts; +} + +rmSync(contentRoot, { recursive: true, force: true }); +mkdirSync(contentRoot, { recursive: true }); + +const copyResults = []; +for (const spec of copySpecs) { + const sourcePath = resolve(repoRoot, spec.from); + const targetPath = resolve(contentRoot, spec.to); + assertWithin(repoRoot, sourcePath); + assertWithin(contentRoot, targetPath); + if (!existsSync(sourcePath)) { + if (spec.required) { + throw new Error(`Missing required content source: ${relative(repoRoot, sourcePath)}`); + } + copyResults.push({ from: spec.from, to: spec.to, required: false, files: 0 }); + continue; + } + const stats = statSync(sourcePath); + const linkStats = lstatSync(sourcePath); + if (linkStats.isSymbolicLink()) { + throw new Error(`Refusing to package symlink: ${relative(repoRoot, sourcePath)}`); + } + let files = 0; + if (stats.isDirectory()) { + files = copyFilteredDirectory(sourcePath, targetPath, spec.extensions); + } else { + mkdirSync(dirname(targetPath), { recursive: true }); + cpSync(sourcePath, targetPath); + files = 1; + } + if (spec.required && files === 0) { + throw new Error(`Required content source copied no files: ${relative(repoRoot, sourcePath)}`); + } + copyResults.push({ from: spec.from, to: spec.to, required: spec.required !== false, files }); +} + +const mcpPackage = readJson(join(packageRoot, 'package.json')); +const sdkPackage = readJson(resolve(repoRoot, 'sdk/ts/package.json')); +const compatPackage = readJson(resolve(repoRoot, 'sdk/ts-compat/package.json')); + +if (mcpPackage.version !== sdkPackage.version || mcpPackage.version !== compatPackage.version) { + throw new Error( + `Strict MCP version policy requires package versions to match: ${mcpPackage.name}@${mcpPackage.version}, ${sdkPackage.name}@${sdkPackage.version}, ${compatPackage.name}@${compatPackage.version}`, + ); +} + +const release = { + schemaVersion: 1, + serverPackage: mcpPackage.name, + mcpName: mcpPackage.mcpName, + serverVersion: mcpPackage.version, + sdkPackage: sdkPackage.name, + sdkVersion: sdkPackage.version, + compatPackage: compatPackage.name, + compatVersion: compatPackage.version, + goModule: goModulePath, + goModuleVersion: `v${mcpPackage.version}`, + sourceCommit: getSourceCommit(), + generatedAt: new Date().toISOString(), + contentMode: 'packaged-snapshot', + versionPolicy: 'strict-mirror', +}; + +writeFileSync(join(contentRoot, 'release.json'), `${JSON.stringify(release, null, 2)}\n`); + +const files = collectContentFiles(contentRoot); +const manifest = { + schemaVersion: 1, + generatedAt: release.generatedAt, + sourceCommit: release.sourceCommit, + contentMode: release.contentMode, + versionPolicy: release.versionPolicy, + copyResults, + counts: { + totalFiles: files.length, + byPrefix: countByPrefix(files), + }, + files, +}; + +writeFileSync(join(contentRoot, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`); + +console.error(`Prepared MCP package content in ${relative(packageRoot, contentRoot)} with ${files.length} files`); diff --git a/sdk/mcp/scripts/set-executable.mjs b/sdk/mcp/scripts/set-executable.mjs new file mode 100644 index 000000000..25c8b6ceb --- /dev/null +++ b/sdk/mcp/scripts/set-executable.mjs @@ -0,0 +1,30 @@ +#!/usr/bin/env node +import { chmodSync, existsSync, lstatSync, statSync } from 'node:fs'; +import { isAbsolute, relative, resolve } from 'node:path'; + +const target = process.argv[2]; + +if (!target) { + throw new Error('Usage: node scripts/set-executable.mjs '); +} + +const targetPath = resolve(process.cwd(), target); +const rel = relative(process.cwd(), targetPath); + +if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) { + throw new Error(`Executable target must stay inside the package directory: ${target}`); +} + +if (!existsSync(targetPath)) { + throw new Error(`Cannot make missing file executable: ${target}`); +} + +if (lstatSync(targetPath).isSymbolicLink()) { + throw new Error(`Executable target must not be a symlink: ${target}`); +} + +if (!statSync(targetPath).isFile()) { + throw new Error(`Executable target is not a file: ${target}`); +} + +chmodSync(targetPath, 0o755); diff --git a/sdk/mcp/scripts/verify-package-content.mjs b/sdk/mcp/scripts/verify-package-content.mjs new file mode 100644 index 000000000..87b3b4729 --- /dev/null +++ b/sdk/mcp/scripts/verify-package-content.mjs @@ -0,0 +1,98 @@ +#!/usr/bin/env node +import { readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = resolve(scriptDir, '..'); +const packJsonPath = resolve(process.cwd(), process.argv[2] ?? 'pack.json'); + +function readJson(path) { + return JSON.parse(readFileSync(path, 'utf-8')); +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const packageJson = readJson(join(packageRoot, 'package.json')); +const serverJson = readJson(join(packageRoot, 'server.json')); +const releaseJson = readJson(join(packageRoot, 'content/release.json')); +const manifestJson = readJson(join(packageRoot, 'content/manifest.json')); +const packJson = readJson(packJsonPath); +const pack = Array.isArray(packJson) ? packJson[0] : packJson; +const paths = new Set(pack.files.map((file) => file.path)); + +const requiredPackageFiles = [ + 'dist/index.js', + 'dist/index.d.ts', + 'content/release.json', + 'content/manifest.json', + 'content/docs/api.yaml', + 'content/docs/protocol/overview.md', + 'content/docs/protocol/cryptography.md', + 'content/docs/protocol/security-and-limitations.md', + 'content/sdk/ts/package.json', + 'content/sdk/ts/src/client.ts', + 'content/sdk/ts/src/index.ts', + 'content/sdk/ts/src/core/types.ts', + 'content/sdk/ts/src/rpc/methods.ts', + 'content/sdk/ts-compat/package.json', + 'content/sdk/ts-compat/src/client.ts', + 'content/sdk/ts-compat/src/index.ts', + 'content/sdk/ts-compat/docs/migration-overview.md', + 'content/sdk/go/client.go', + 'content/sdk/go/README.md', + 'content/pkg/app/app_v1.go', + 'content/pkg/core/types.go', + 'content/pkg/rpc/methods.go', + 'README.md', + 'package.json', + 'server.json', +]; + +for (const path of requiredPackageFiles) { + assert(paths.has(path), `missing package file: ${path}`); +} + +// Defense-in-depth against a future package.json files allowlist expansion. +for (const path of paths) { + assert(!path.startsWith('src/'), `source-only file leaked into package: ${path}`); + assert(!path.startsWith('scripts/'), `script file leaked into package: ${path}`); + assert(!path.endsWith('.test.ts'), `test file leaked into package: ${path}`); + assert(!path.endsWith('.spec.ts'), `test file leaked into package: ${path}`); + assert(!path.endsWith('_test.go'), `test file leaked into package: ${path}`); +} + +assert(serverJson.name === packageJson.mcpName, `server.json name ${serverJson.name} does not match package.json mcpName ${packageJson.mcpName}`); +const npmPackage = serverJson.packages.find((entry) => entry.registryType === 'npm'); +assert(npmPackage, 'server.json is missing an npm package entry'); +assert(npmPackage.identifier === packageJson.name, `server.json package ${npmPackage.identifier} does not match package.json name ${packageJson.name}`); +assert(serverJson.version === packageJson.version, 'server.json version must match package.json version'); +assert(npmPackage.version === packageJson.version, 'server.json npm package version must match package.json version'); + +assert(releaseJson.serverPackage === packageJson.name, 'release.json serverPackage must match package.json name'); +assert(releaseJson.mcpName === packageJson.mcpName, 'release.json mcpName must match package.json mcpName'); +assert(releaseJson.serverVersion === packageJson.version, 'release.json serverVersion must match package.json version'); +assert(releaseJson.sdkVersion === packageJson.version, 'release.json sdkVersion must match MCP package version under strict-mirror policy'); +assert(releaseJson.compatVersion === packageJson.version, 'release.json compatVersion must match MCP package version under strict-mirror policy'); +assert(releaseJson.goModule === 'github.com/layer-3/nitrolite', 'release.json goModule is unexpected'); +assert(releaseJson.goModuleVersion === `v${packageJson.version}`, `release.json goModuleVersion must be v${packageJson.version}`); +assert(releaseJson.contentMode === 'packaged-snapshot', 'release.json contentMode must be packaged-snapshot'); +assert(releaseJson.versionPolicy === 'strict-mirror', 'release.json versionPolicy must be strict-mirror'); +assert(typeof releaseJson.sourceCommit === 'string' && releaseJson.sourceCommit.length > 0, 'release.json sourceCommit must be present'); + +assert(manifestJson.contentMode === 'packaged-snapshot', 'manifest contentMode must be packaged-snapshot'); +assert(manifestJson.versionPolicy === 'strict-mirror', 'manifest versionPolicy must be strict-mirror'); +assert(Array.isArray(manifestJson.files), 'manifest files must be an array'); +assert(manifestJson.files.length >= 70, `manifest contains too few files: ${manifestJson.files.length}`); +assert(manifestJson.counts?.totalFiles === manifestJson.files.length, 'manifest totalFiles must match files length'); + +for (const file of manifestJson.files) { + const packagePath = `content/${file.path}`; + assert(paths.has(packagePath), `manifest file missing from package: ${packagePath}`); + assert(typeof file.sha256 === 'string' && /^[a-f0-9]{64}$/.test(file.sha256), `manifest file has invalid sha256: ${file.path}`); + assert(typeof file.size === 'number' && file.size >= 0, `manifest file has invalid size: ${file.path}`); +} + +console.error(`Verified ${pack.filename} with ${manifestJson.files.length} manifest files`); diff --git a/sdk/mcp/scripts/verify-release-preflight.mjs b/sdk/mcp/scripts/verify-release-preflight.mjs new file mode 100644 index 000000000..336a85fbb --- /dev/null +++ b/sdk/mcp/scripts/verify-release-preflight.mjs @@ -0,0 +1,68 @@ +#!/usr/bin/env node +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = resolve(scriptDir, '..'); + +function readJson(path) { + return JSON.parse(readFileSync(path, 'utf-8')); +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +function run(command, args) { + return execFileSync(command, args, { + cwd: packageRoot, + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'pipe'], + }).trim(); +} + +function npmVersionExists(packageName, version) { + let resolved; + try { + resolved = run('npm', ['view', `${packageName}@${version}`, 'version']); + } catch { + throw new Error(`${packageName}@${version} is not available on npm`); + } + assert(resolved === version, `${packageName}@${version} is not available on npm`); +} + +function goTagExists(tag) { + try { + run('git', ['ls-remote', '--exit-code', 'origin', `refs/tags/${tag}`]); + } catch { + throw new Error(`Go module tag ${tag} is not available on origin`); + } +} + +const packageJson = readJson(join(packageRoot, 'package.json')); +const serverJson = readJson(join(packageRoot, 'server.json')); +const releaseJson = readJson(join(packageRoot, 'content/release.json')); +const version = packageJson.version; +const expectedMcpTag = `mcp-v${version}`; +const expectedGoTag = `v${version}`; + +assert(serverJson.version === version, 'server.json version must match package.json version'); +const npmPackage = serverJson.packages.find((entry) => entry.registryType === 'npm'); +assert(npmPackage?.version === version, 'server.json npm package version must match package.json version'); +assert(releaseJson.serverVersion === version, 'release.json serverVersion must match package.json version'); +assert(releaseJson.sdkVersion === version, 'release.json sdkVersion must match package.json version'); +assert(releaseJson.compatVersion === version, 'release.json compatVersion must match package.json version'); +assert(releaseJson.goModuleVersion === expectedGoTag, `release.json goModuleVersion must be ${expectedGoTag}`); +assert(releaseJson.versionPolicy === 'strict-mirror', 'release.json versionPolicy must be strict-mirror'); + +if (process.env.GITHUB_REF_NAME) { + assert(process.env.GITHUB_REF_NAME === expectedMcpTag, `release tag ${process.env.GITHUB_REF_NAME} must be ${expectedMcpTag}`); +} + +goTagExists(expectedGoTag); +npmVersionExists('@yellow-org/sdk', version); +npmVersionExists('@yellow-org/sdk-compat', version); + +console.error(`Release preflight passed for ${packageJson.name}@${version}`); diff --git a/sdk/mcp/server.json b/sdk/mcp/server.json new file mode 100644 index 000000000..e1a24ef81 --- /dev/null +++ b/sdk/mcp/server.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.layer-3/yellow-sdk-mcp", + "description": "MCP server exposing Yellow SDK and Nitrolite protocol reference material to AI agents and IDEs.", + "version": "1.3.0", + "repository": { + "url": "https://github.com/layer-3/nitrolite", + "source": "github" + }, + "packages": [ + { + "registryType": "npm", + "identifier": "@yellow-org/sdk-mcp", + "version": "1.3.0", + "transport": { + "type": "stdio" + } + } + ] +} diff --git a/sdk/mcp/src/index.ts b/sdk/mcp/src/index.ts new file mode 100644 index 000000000..80f593e5e --- /dev/null +++ b/sdk/mcp/src/index.ts @@ -0,0 +1,2460 @@ +#!/usr/bin/env node +/** + * Yellow SDK MCP Server + * + * Exposes the Nitrolite SDK API surface to AI agents and IDEs via the + * Model Context Protocol. Reads SDK source at startup to build structured + * knowledge of methods, types, enums, examples, and protocol docs. Falls back + * to the packaged release snapshot when the monorepo is unavailable. + * + * Naming note: the off-chain broker was named "clearnode" through v1.2.0 + * and renamed to "nitronode" in v1.3.0. See `nitrolite://migration/nitronode`. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { readFileSync, existsSync } from 'node:fs'; +import { resolve, dirname, isAbsolute, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PACKAGE_ROOT = resolve(__dirname, '..'); +const CONTENT_ROOT = resolve(PACKAGE_ROOT, 'content'); +const SDK_ROOT = resolve(__dirname, '../../ts'); +const COMPAT_ROOT = resolve(__dirname, '../../ts-compat'); +const REPO_ROOT = resolve(__dirname, '../../..'); +const PROTOCOL_DOCS = resolve(REPO_ROOT, 'docs/protocol'); +const API_YAML = resolve(REPO_ROOT, 'docs/api.yaml'); +const GO_SDK_ROOT = resolve(REPO_ROOT, 'sdk/go'); +const PKG_ROOT = resolve(REPO_ROOT, 'pkg'); +const GO_MODULE_PATH = 'github.com/layer-3/nitrolite'; +const SERVER_NAME = 'yellow-sdk-mcp'; + +const SOURCE_ROOTS: Array<{ root: string; contentPrefix: string }> = [ + { root: SDK_ROOT, contentPrefix: 'sdk/ts' }, + { root: COMPAT_ROOT, contentPrefix: 'sdk/ts-compat' }, + { root: PROTOCOL_DOCS, contentPrefix: 'docs/protocol' }, + { root: GO_SDK_ROOT, contentPrefix: 'sdk/go' }, + { root: PKG_ROOT, contentPrefix: 'pkg' }, + { root: REPO_ROOT, contentPrefix: '' }, +]; +const MONOREPO_SOURCE_AVAILABLE = existsSync(resolve(SDK_ROOT, 'src/client.ts')); + +// --------------------------------------------------------------------------- +// Helpers — read SDK sources at startup +// --------------------------------------------------------------------------- + +function isWithin(root: string, path: string): boolean { + const rel = relative(root, path); + return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel)); +} + +function fallbackContentPath(path: string): string | undefined { + for (const { root, contentPrefix } of SOURCE_ROOTS) { + const rel = relative(root, path); + if (!isWithin(root, path)) continue; + if (rel === '') continue; + return resolve(CONTENT_ROOT, contentPrefix, rel); + } + return undefined; +} + +function readFile(path: string): string { + if (existsSync(path) && (MONOREPO_SOURCE_AVAILABLE || isWithin(PACKAGE_ROOT, path))) { + return readFileSync(path, 'utf-8'); + } + + const packagedPath = fallbackContentPath(path); + if (packagedPath && existsSync(packagedPath)) { + return readFileSync(packagedPath, 'utf-8'); + } + + return ''; +} + +function readPackageVersion(path: string): string | undefined { + const content = readFile(path); + if (!content) return undefined; + try { + const parsed = JSON.parse(content) as { version?: unknown }; + return typeof parsed.version === 'string' ? parsed.version : undefined; + } catch { + return undefined; + } +} + +interface ReleaseMetadata { + schemaVersion: number; + serverPackage: string; + mcpName: string; + serverVersion: string; + sdkPackage: string; + sdkVersion: string; + compatPackage: string; + compatVersion: string; + goModule: string; + goModuleVersion: string; + sourceCommit: string; + generatedAt: string; + contentMode: 'packaged-snapshot'; + versionPolicy: 'strict-mirror'; +} + +interface ContentManifest { + files?: Array<{ path: string; size: number; sha256: string }>; + counts?: { totalFiles?: number; byPrefix?: Record }; +} + +function readJsonFile(path: string): T | undefined { + const content = readFile(path); + if (!content) return undefined; + try { + return JSON.parse(content) as T; + } catch { + return undefined; + } +} + +function deriveGoModuleVersion(version: string): string { + return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version) ? `v${version}` : 'unknown'; +} + +/** Extract named exports from a barrel file */ +function extractExports(content: string): string[] { + const names: string[] = []; + // Match: export { Foo, type Bar } from '...' and export type { Baz } from '...' + for (const m of content.matchAll(/export\s+(type\s+)?\{([^}]+)\}/g)) { + const groupIsTypeOnly = Boolean(m[1]); + for (const item of m[2].split(',')) { + const raw = item.trim(); + if (!raw || raw.startsWith('//')) continue; + + const itemIsTypeOnly = groupIsTypeOnly || raw.startsWith('type '); + const spec = raw.replace(/^type\s+/, '').trim(); + const aliasMatch = spec.match(/^(\w+)(?:\s+as\s+(\w+))?$/); + if (!aliasMatch) continue; + + const exportedName = aliasMatch[2] || aliasMatch[1]; + names.push(itemIsTypeOnly ? `type ${exportedName}` : exportedName); + } + } + // Match: export * from '...' + for (const m of content.matchAll(/export\s+\*\s+from\s+'([^']+)'/g)) { + names.push(`* from '${m[1]}'`); + } + return names; +} + +function findNamedExport(exports: string[], symbol: string): { found: boolean; isTypeOnly: boolean } { + if (exports.includes(`type ${symbol}`)) return { found: true, isTypeOnly: true }; + if (exports.includes(symbol)) return { found: true, isTypeOnly: false }; + return { found: false, isTypeOnly: false }; +} + +function renderImportStatement(pkg: string, symbol: string, isTypeOnly: boolean): string { + return isTypeOnly + ? `import type { ${symbol} } from '${pkg}';` + : `import { ${symbol} } from '${pkg}';`; +} + +// --------------------------------------------------------------------------- +// SDK Data — populated at startup +// --------------------------------------------------------------------------- + +interface MethodInfo { + name: string; + signature: string; + description: string; + category: string; +} + +interface TypeInfo { + name: string; + kind: 'interface' | 'type' | 'enum' | 'class'; + fields: string; + source: string; +} + +const methods: MethodInfo[] = []; +const types: TypeInfo[] = []; +const compatExports: string[] = []; + +// Go SDK data — populated at startup +interface GoTypeInfo { + name: string; + kind: 'struct' | 'enum' | 'type'; + fields: string; + source: string; +} + +interface GoMethodInfo { + name: string; + signature: string; + comment: string; + category: string; +} + +const goTypes: GoTypeInfo[] = []; +const goMethods: GoMethodInfo[] = []; + +// Protocol docs loaded at startup +const protocolDocs: Record = {}; + +// Terminology concept → definition map +const concepts: Map = new Map(); + +// V1 RPC method → { description, request fields, response fields } map (from docs/api.yaml) +interface RPCMethodDoc { + /** Fully qualified v1 method name, e.g. "channels.v1.get_home_channel" */ + method: string; + /** API group, e.g. "channels" */ + group: string; + description: string; + requestFields: string; + responseFields: string; +} +const rpcMethodDocs: Map = new Map(); + +function loadClientMethods(): void { + const content = readFile(resolve(SDK_ROOT, 'src/client.ts')); + const re = /\/\*\*\s*([\s\S]*?)\*\/\s*(?:(public|protected|private)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*Promise<([^>]+)>|\s*:\s*(\S+))?/g; + let match; + while ((match = re.exec(content)) !== null) { + const doc = match[1].replace(/\s*\*\s*/g, ' ').trim(); + const visibility = match[2] ?? 'public'; + const name = match[3]; + const params = match[4].trim(); + const returnType = match[5] || match[6] || 'void'; + + // Index only public client methods. + if (visibility !== 'public' || name.startsWith('_')) continue; + + const category = categorizeMethod(name); + const isAsync = /\basync\b/.test(match[0]); + const returnStr = isAsync ? `Promise<${returnType}>` : returnType; + + methods.push({ + name, + signature: `${name}(${params}): ${returnStr}`, + description: doc || `SDK method: ${name}`, + category, + }); + } +} + +function categorizeMethod(name: string): string { + if (/channel/i.test(name)) return 'Channels'; + if (/deposit|withdraw|transfer|escrow|approve/i.test(name)) return 'Transactions'; + if (/app.*session|submitApp/i.test(name)) return 'App Sessions'; + if (/sign|signer|key/i.test(name)) return 'Signing'; + if (/ping|config|asset|balance|blockchain/i.test(name)) return 'Node & Queries'; + return 'Other'; +} + +function categorizeGoMethod(name: string): string { + const lower = name.toLowerCase(); + if (/channel|deposit|withdraw|transfer|checkpoint|challenge|acknowledge|close/.test(lower)) return 'Channels & Transactions'; + if (/appsession|appstate|appdef|rebalance/.test(lower)) return 'App Sessions'; + if (/sessionkey|keystate/.test(lower)) return 'Session Keys'; + if (/escrow|security|locked/.test(lower)) return 'Security Tokens'; + if (/app/.test(lower) && /register/.test(lower)) return 'App Registry'; + if (/balance|transaction|allowance|user/.test(lower)) return 'User Queries'; + if (/config|blockchain|asset|ping/.test(lower)) return 'Node & Config'; + return 'Other'; +} + +function loadGoTypes(): void { + const fileSets = [ + { path: resolve(PKG_ROOT, 'core/types.go'), source: 'pkg/core' }, + { path: resolve(PKG_ROOT, 'app/app_session_v1.go'), source: 'pkg/app' }, + { path: resolve(PKG_ROOT, 'rpc/types.go'), source: 'pkg/rpc' }, + { path: resolve(GO_SDK_ROOT, 'config.go'), source: 'sdk/go' }, + { path: resolve(GO_SDK_ROOT, 'app_session.go'), source: 'sdk/go' }, + { path: resolve(GO_SDK_ROOT, 'app_registry.go'), source: 'sdk/go' }, + { path: resolve(GO_SDK_ROOT, 'user.go'), source: 'sdk/go' }, + { path: resolve(GO_SDK_ROOT, 'channel.go'), source: 'sdk/go' }, + ]; + + for (const { path, source } of fileSets) { + const content = readFile(path); + if (!content) continue; + + // Pass 1: collect enum-like type declarations (name → kind) + const enumTypeMap = new Map(); + for (const m of content.matchAll(/type\s+([A-Z]\w+)\s+(uint\d+|int\d+|string)\s*\n/g)) { + const info: GoTypeInfo = { name: m[1], kind: 'enum', fields: '', source }; + enumTypeMap.set(m[1], info); + } + + // Pass 2: parse const(...) and var(...) blocks to collect enum values + for (const blockMatch of content.matchAll(/(?:const|var)\s*\(([^)]+)\)/gs)) { + const block = blockMatch[1]; + let currentType: string | undefined; + + for (const rawLine of block.split('\n')) { + const line = rawLine.replace(/\/\/.*$/, '').trim(); + if (!line) continue; // blank lines don't reset currentType + + // Fully-annotated: ExportedName TypeName = ... or ExportedName TypeName + const fullAnnotated = line.match(/^([A-Z]\w+)\s+([A-Z]\w+)\s*(?:=.*)?$/); + if (fullAnnotated) { + const [, valueName, typeName] = fullAnnotated; + if (enumTypeMap.has(typeName)) { + currentType = typeName; + const info = enumTypeMap.get(typeName)!; + info.fields += (info.fields ? '\n' : '') + valueName; + continue; + } + } + + // Untyped-assignment: ExportedName = value (no type annotation) + const untypedAssign = line.match(/^([A-Z]\w+)\s*=\s*(.+)$/); + if (untypedAssign) { + const [, valueName] = untypedAssign; + let resolvedType = currentType; + if (!resolvedType) { + // Prefix inference: find enum type whose name is a prefix of valueName + for (const typeName of enumTypeMap.keys()) { + if (valueName.startsWith(typeName) && valueName[typeName.length]?.match(/[A-Z_]/)) { + resolvedType = typeName; + break; + } + } + } + if (resolvedType && enumTypeMap.has(resolvedType)) { + currentType = resolvedType; + const info = enumTypeMap.get(resolvedType)!; + info.fields += (info.fields ? '\n' : '') + valueName; + } + continue; + } + + // Bare identifier: ExportedName (iota follow-on) + const bareIdent = line.match(/^([A-Z]\w+)\s*$/); + if (bareIdent && currentType && enumTypeMap.has(currentType)) { + const info = enumTypeMap.get(currentType)!; + info.fields += (info.fields ? '\n' : '') + bareIdent[1]; + continue; + } + + // Non-exported or unrecognised → reset + if (!line.match(/^[A-Z]/)) currentType = undefined; + } + } + for (const info of enumTypeMap.values()) { + if (info.fields) goTypes.push(info); + } + + // Structs + for (const m of content.matchAll(/(?:\/\/[^\n]*\n)*type\s+([A-Z]\w+)\s+struct\s*\{([^}]*)\}/gs)) { + goTypes.push({ name: m[1], kind: 'struct', fields: m[2].trim(), source }); + } + + // Functional option types (e.g. type Option func(*Config)) + for (const m of content.matchAll(/type\s+([A-Z]\w+)\s+(func\([^)]*\)[^\n]*)/g)) { + goTypes.push({ name: m[1], kind: 'type', fields: m[2].trim(), source }); + } + } +} + +function loadGoSdkMethods(): void { + // Prepend NewClient constructor (no receiver) + goMethods.push({ + name: 'NewClient', + signature: 'func NewClient(wsURL string, stateSigner core.ChannelSigner, rawSigner sign.Signer, opts ...Option) (*Client, error)', + comment: 'Creates a new Nitrolite SDK client connected to a nitronode', + category: 'Connection', + }); + + const files = [ + { path: resolve(GO_SDK_ROOT, 'channel.go'), category: '' }, + { path: resolve(GO_SDK_ROOT, 'node.go'), category: 'Node & Config' }, + { path: resolve(GO_SDK_ROOT, 'user.go'), category: 'User Queries' }, + { path: resolve(GO_SDK_ROOT, 'app_session.go'), category: 'App Sessions' }, + { path: resolve(GO_SDK_ROOT, 'app_registry.go'), category: 'App Registry' }, + { path: resolve(GO_SDK_ROOT, 'client.go'), category: '' }, + ]; + + const methodRe = /((?:\/\/[^\n]*\n)+)func \(c \*Client\) (\w+)\(([^)]*)\)\s*(.*)/g; + + for (const { path, category } of files) { + const content = readFile(path); + if (!content) continue; + methodRe.lastIndex = 0; + let match; + while ((match = methodRe.exec(content)) !== null) { + const rawComment = match[1].trim(); + const comment = rawComment.split('\n') + .map(l => l.replace(/^\/\/ ?/, '').trim()) + .filter(Boolean) + .join(' '); + const name = match[2]; + const params = match[3]; + const returns = match[4].trim(); + if (name[0] >= 'a' && name[0] <= 'z') continue; // skip unexported + const cat = category || categorizeGoMethod(name); + goMethods.push({ + name, + signature: `func (c *Client) ${name}(${params}) ${returns}`, + comment, + category: cat, + }); + } + } +} + +function buildGoApiMethodsContent(): string { + const ORDER = ['Connection', 'Channels & Transactions', 'App Sessions', 'Session Keys', 'Security Tokens', 'App Registry', 'User Queries', 'Node & Config', 'Other']; + const grouped = new Map(); + for (const m of goMethods) { + const arr = grouped.get(m.category) ?? []; + arr.push(m); + grouped.set(m.category, arr); + } + let text = '# Nitrolite Go SDK — Client Methods\n\nPackage: `github.com/layer-3/nitrolite/sdk/go`\n\n'; + for (const cat of ORDER) { + const ms = grouped.get(cat); + if (!ms?.length) continue; + text += `## ${cat}\n\n`; + for (const m of ms) { + text += `### \`${m.name}\`\n\`\`\`go\n${m.signature}\n\`\`\`\n${m.comment}\n\n`; + } + } + return text; +} + +function buildGoTypesContent(): string { + const bySource = new Map(); + for (const t of goTypes) { + const arr = bySource.get(t.source) ?? []; + arr.push(t); + bySource.set(t.source, arr); + } + let text = '# Nitrolite Go SDK — Types\n\n'; + for (const [src, ts] of [...bySource.entries()].sort()) { + text += `## ${src}\n\n`; + for (const t of ts) { + if (t.kind === 'struct') { + text += `### \`${t.name}\` (struct)\n\`\`\`go\ntype ${t.name} struct {\n${t.fields}\n}\n\`\`\`\n\n`; + } else if (t.kind === 'enum') { + text += `### \`${t.name}\` (enum)\n**Values:**\n${t.fields.split('\n').map(v => `- \`${v}\``).join('\n')}\n\n`; + } else { + text += `### \`${t.name}\` (${t.kind})\n\`\`\`go\ntype ${t.name} ${t.fields}\n\`\`\`\n\n`; + } + } + } + return text; +} + +function loadTypes(): void { + const files = [ + { path: resolve(SDK_ROOT, 'src/core/types.ts'), source: 'sdk/ts (core)' }, + { path: resolve(SDK_ROOT, 'src/rpc/types.ts'), source: 'sdk/ts (rpc)' }, + { path: resolve(SDK_ROOT, 'src/app/types.ts'), source: 'sdk/ts (app)' }, + { path: resolve(COMPAT_ROOT, 'src/types.ts'), source: 'sdk-compat' }, + ]; + + for (const { path, source } of files) { + const content = readFile(path); + if (!content) continue; + + // Enums + for (const m of content.matchAll(/export\s+enum\s+(\w+)\s*\{([^}]+)\}/g)) { + types.push({ name: m[1], kind: 'enum', fields: m[2].trim(), source }); + } + // Interfaces + for (const m of content.matchAll(/export\s+interface\s+(\w+)(?:\s+extends\s+\w+)?\s*\{([^}]+)\}/g)) { + types.push({ name: m[1], kind: 'interface', fields: m[2].trim(), source }); + } + // Type aliases + for (const m of content.matchAll(/export\s+type\s+(\w+)\s*=\s*([^;]+);/g)) { + types.push({ name: m[1], kind: 'type', fields: m[2].trim(), source }); + } + } +} + +function loadCompatExports(): void { + const content = readFile(resolve(COMPAT_ROOT, 'src/index.ts')); + compatExports.push(...extractExports(content)); +} + +function loadProtocolDocs(): void { + const files = [ + 'overview.md', 'terminology.md', 'cryptography.md', 'state-model.md', + 'channel-protocol.md', 'enforcement.md', 'cross-chain-and-assets.md', + 'interactions.md', 'security-and-limitations.md', + ]; + for (const file of files) { + const key = file.replace('.md', ''); + const content = readFile(resolve(PROTOCOL_DOCS, file)); + if (content) protocolDocs[key] = content; + } +} + +function loadTerminology(): void { + const content = protocolDocs['terminology'] || ''; + // Parse ### headings and their following paragraphs + const sections = content.split(/^### /m).slice(1); + for (const section of sections) { + const lines = section.trim().split('\n'); + const name = lines[0].trim(); + const body = lines.slice(1).join('\n').trim() + .replace(/^[\s\n]+/, '') + .split(/\n(?=### |## )/)[0] + .trim(); + if (name && body) { + concepts.set(name.toLowerCase(), `**${name}**\n\n${body}`); + } + } +} + +function loadV1API(): void { + const content = readFile(API_YAML); + if (!content) return; + + // Simple line-based parser for the well-structured api.yaml + // Extracts: group name, method name, description, request fields, response fields + let currentGroup = ''; + let currentMethod = ''; + let currentDesc = ''; + let currentSection: 'none' | 'request' | 'response' = 'none'; + let requestFields: string[] = []; + let responseFields: string[] = []; + + const flushMethod = () => { + if (currentGroup && currentMethod) { + const fqn = `${currentGroup}.v1.${currentMethod}`; + rpcMethodDocs.set(fqn, { + method: fqn, + group: currentGroup, + description: currentDesc, + requestFields: requestFields.length > 0 ? requestFields.join(', ') : '(none)', + responseFields: responseFields.length > 0 ? responseFields.join(', ') : '(none)', + }); + } + currentMethod = ''; + currentDesc = ''; + currentSection = 'none'; + requestFields = []; + responseFields = []; + }; + + // Only parse the api: section + const apiStart = content.indexOf('\napi:\n'); + if (apiStart === -1) return; + const lines = content.slice(apiStart).split('\n'); + + for (const line of lines) { + // Group: " - name: channels" + const groupMatch = line.match(/^ {4}- name:\s+(.+)/); + if (groupMatch) { + flushMethod(); + currentGroup = groupMatch[1].trim(); + continue; + } + + // Method: " - name: get_home_channel" + const methodMatch = line.match(/^ {12}- name:\s+(.+)/); + if (methodMatch) { + flushMethod(); + currentMethod = methodMatch[1].trim(); + continue; + } + + // Method description: " description: ..." + if (currentMethod && !currentDesc) { + const descMatch = line.match(/^ {14}description:\s+(.+)/); + if (descMatch) { + currentDesc = descMatch[1].trim(); + continue; + } + } + + // Request/response section markers + if (currentMethod) { + const sectionMatch = line.match(/^ {14}(request|response):/); + if (sectionMatch) { + currentSection = sectionMatch[1] as 'request' | 'response'; + continue; + } + + // Field name within request/response: " - field_name: wallet" + const fieldMatch = line.match(/^ {16}- field_name:\s+(.+)/); + if (fieldMatch) { + if (currentSection === 'request') requestFields.push(fieldMatch[1].trim()); + else if (currentSection === 'response') responseFields.push(fieldMatch[1].trim()); + continue; + } + + // errors: or events: sections end request/response + if (/^ {14}(errors|events):/.test(line)) { + currentSection = 'none'; + } + } + } + flushMethod(); // flush last method +} + +// --------------------------------------------------------------------------- +// Go SDK static content constants ported from the former Go MCP server. +// --------------------------------------------------------------------------- + +const AUTH_FLOW_CONTENT = `# Request Signing & Authorization + +In v1, every RPC request includes a \`sig\` field — the client's signature over the entire \`req\` tuple. This is the authorization mechanism. There is no separate authentication handshake; request signatures are the identity proof. + +## Session Keys + +Session keys enable delegated signing with scoped permissions. They are managed via: +- \`channels.v1.submit_session_key_state\` — register/update channel session keys +- \`app_sessions.v1.submit_session_key_state\` — register/update app session keys + +Session keys have: +- Per-asset allowances with spending caps +- Expiration timestamps +- Scoping to specific applications and app sessions + +## Wire Format + +\`\`\`json +// Every request is signed +{ "req": [REQUEST_ID, "channels.v1.submit_state", { ... }, TIMESTAMP], "sig": ["0xClientSignature..."] } + +// Server responds with its own signature +{ "res": [REQUEST_ID, "channels.v1.submit_state", { ... }, TIMESTAMP], "sig": ["0xServerSignature..."] } +\`\`\` + +## Note on 0.5.x Compat + +The \`@yellow-org/sdk-compat\` layer exposes legacy auth helpers (\`createAuthRequestMessage\`, \`createAuthVerifyMessage\`) that implement a challenge-response flow with JWT. This is the 0.5.x auth surface bridged to v1. New applications using \`@yellow-org/sdk\` directly do not use this flow. +`; + +const GO_TRANSFER_EXAMPLE = `# Complete Go Transfer Script + +\`\`\`go +package main + +import ( + "context" + "log" + "os" + "time" + + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" + "github.com/shopspring/decimal" +) + +func main() { + privateKey := os.Getenv("PRIVATE_KEY") + nitronodeURL := os.Getenv("NITRONODE_URL") + rpcURL := os.Getenv("RPC_URL") + recipient := os.Getenv("RECIPIENT") + var chainID uint64 = 11155111 // Sepolia + + stateSigner, err := sign.NewEthereumMsgSigner(privateKey) + if err != nil { log.Fatal(err) } + txSigner, err := sign.NewEthereumRawSigner(privateKey) + if err != nil { log.Fatal(err) } + + client, err := sdk.NewClient(nitronodeURL, stateSigner, txSigner, + sdk.WithBlockchainRPC(chainID, rpcURL), + ) + if err != nil { log.Fatal(err) } + defer client.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + // Approve token spending (one-time) + _, err = client.ApproveToken(ctx, chainID, "usdc", decimal.NewFromInt(1000)) + if err != nil { log.Fatal(err) } + + // Deposit — creates channel if needed + state, err := client.Deposit(ctx, chainID, "usdc", decimal.NewFromInt(10)) + if err != nil { log.Fatal(err) } + log.Printf("Deposited 10 USDC, state version: %d", state.Version) + + // Checkpoint on-chain + txHash, err := client.Checkpoint(ctx, "usdc") + if err != nil { log.Fatal(err) } + log.Printf("On-chain tx: %s", txHash) + + // Transfer + _, err = client.Transfer(ctx, recipient, "usdc", decimal.NewFromInt(5)) + if err != nil { log.Fatal(err) } + log.Println("Transferred 5 USDC") + + // Close channel — prepare finalize state, then checkpoint + _, err = client.CloseHomeChannel(ctx, "usdc") + if err != nil { log.Fatal(err) } + closeTx, err := client.Checkpoint(ctx, "usdc") + if err != nil { log.Fatal(err) } + log.Printf("Channel closed, tx: %s", closeTx) +} +\`\`\` + +## Environment Variables + +- \`PRIVATE_KEY\` — Hex private key (without 0x prefix) +- \`NITRONODE_URL\` — WebSocket URL +- \`RPC_URL\` — Ethereum RPC endpoint +- \`RECIPIENT\` — Recipient address +`; + +const GO_APP_SESSION_EXAMPLE = `# Complete Go App Session Script + +\`\`\`go +package main + +import ( + "context" + "log" + "os" + "strconv" + "time" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" + "github.com/shopspring/decimal" +) + +func main() { + privateKey := os.Getenv("PRIVATE_KEY") + nitronodeURL := os.Getenv("NITRONODE_URL") + rpcURL := os.Getenv("RPC_URL") + peerAddr := os.Getenv("PEER_ADDRESS") + var chainID uint64 = 11155111 + + stateSigner, err := sign.NewEthereumMsgSigner(privateKey) + if err != nil { log.Fatal(err) } + txSigner, err := sign.NewEthereumRawSigner(privateKey) + if err != nil { log.Fatal(err) } + + client, err := sdk.NewClient(nitronodeURL, stateSigner, txSigner, + sdk.WithBlockchainRPC(chainID, rpcURL), + ) + if err != nil { log.Fatal(err) } + defer client.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + myAddr := client.GetUserAddress() + + // Fund the home channel before moving funds into the app session + if _, err = client.Deposit(ctx, chainID, "usdc", decimal.NewFromInt(20)); err != nil { log.Fatal(err) } + txHash, err := client.Checkpoint(ctx, "usdc") + if err != nil { log.Fatal(err) } + log.Printf("Home channel funded with 20 USDC, tx: %s", txHash) + + // 1. Create app session + definition := app.AppDefinitionV1{ + ApplicationID: "my-game", + Participants: []app.AppParticipantV1{ + {WalletAddress: myAddr, SignatureWeight: 50}, + {WalletAddress: peerAddr, SignatureWeight: 50}, + }, + Quorum: 100, + Nonce: 1, + } + + quorumSigs := []string{"0xMySignature...", "0xPeerSignature..."} + sessionID, versionStr, status, err := client.CreateAppSession(ctx, definition, "{}", quorumSigs) + if err != nil { log.Fatal(err) } + log.Printf("Session %s created (version: %s, status: %s)", sessionID, versionStr, status) + + initVersion, err := strconv.ParseUint(versionStr, 10, 64) + if err != nil { log.Fatal(err) } + + // 2. Fund the app session before submitting non-zero allocations + depositUpdate := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentDeposit, + Version: initVersion + 1, + Allocations: []app.AppAllocationV1{ + {Participant: myAddr, Asset: "usdc", Amount: decimal.NewFromInt(15)}, + {Participant: peerAddr, Asset: "usdc", Amount: decimal.NewFromInt(5)}, + }, + SessionData: "{}", + } + if _, err = client.SubmitAppSessionDeposit(ctx, depositUpdate, quorumSigs, "usdc", decimal.NewFromInt(20)); err != nil { log.Fatal(err) } + log.Println("Session funded with 20 USDC") + + // 3. Submit state update (version = initial + 2) + update := app.AppStateUpdateV1{ + AppSessionID: sessionID, + Intent: app.AppStateUpdateIntentOperate, + Version: initVersion + 2, + Allocations: depositUpdate.Allocations, + SessionData: "{\"round\":1,\"winner\":\"me\"}", + } + operateSigs := []string{"0xMySig...", "0xPeerSig..."} + if err = client.SubmitAppState(ctx, update, operateSigs); err != nil { log.Fatal(err) } + log.Println("State updated") + + // 4. Close session — submit with Close intent (version = initial + 3) + closeUpdate := update + closeUpdate.Intent = app.AppStateUpdateIntentClose + closeUpdate.Version = initVersion + 3 + closeSigs := []string{"0xMyCloseSig...", "0xPeerCloseSig..."} + if err = client.SubmitAppState(ctx, closeUpdate, closeSigs); err != nil { log.Fatal(err) } + log.Println("Session closed") +} +\`\`\` +`; + +const GO_SCAFFOLD_TRANSFER = `package main + +import ( +\t"context" +\t"log" +\t"os" +\t"time" + +\t"github.com/layer-3/nitrolite/pkg/sign" +\tsdk "github.com/layer-3/nitrolite/sdk/go" +\t"github.com/shopspring/decimal" +) + +func main() { +\tstateSigner, err := sign.NewEthereumMsgSigner(os.Getenv("PRIVATE_KEY")) +\tif err != nil { log.Fatal(err) } +\ttxSigner, err := sign.NewEthereumRawSigner(os.Getenv("PRIVATE_KEY")) +\tif err != nil { log.Fatal(err) } + +\tclient, err := sdk.NewClient(os.Getenv("NITRONODE_URL"), stateSigner, txSigner, +\t\tsdk.WithBlockchainRPC(11155111, os.Getenv("RPC_URL")), +\t) +\tif err != nil { log.Fatal(err) } +\tdefer client.Close() + +\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) +\tdefer cancel() + +\t_, err = client.Deposit(ctx, 11155111, "usdc", decimal.NewFromInt(10)) +\tif err != nil { log.Fatal(err) } +\ttxHash, err := client.Checkpoint(ctx, "usdc") +\tif err != nil { log.Fatal(err) } +\tlog.Printf("Deposited 10 USDC, tx: %s", txHash) + +\t_, err = client.Transfer(ctx, os.Getenv("RECIPIENT"), "usdc", decimal.NewFromInt(5)) +\tif err != nil { log.Fatal(err) } +\tlog.Println("Transferred 5 USDC") +} +`; + +const GO_SCAFFOLD_APP_SESSION = `package main + +import ( +\t"context" +\t"log" +\t"os" +\t"strconv" +\t"time" + +\t"github.com/layer-3/nitrolite/pkg/app" +\t"github.com/layer-3/nitrolite/pkg/sign" +\tsdk "github.com/layer-3/nitrolite/sdk/go" +\t"github.com/shopspring/decimal" +) + +func main() { +\tstateSigner, err := sign.NewEthereumMsgSigner(os.Getenv("PRIVATE_KEY")) +\tif err != nil { log.Fatal(err) } +\ttxSigner, err := sign.NewEthereumRawSigner(os.Getenv("PRIVATE_KEY")) +\tif err != nil { log.Fatal(err) } + +\tclient, err := sdk.NewClient(os.Getenv("NITRONODE_URL"), stateSigner, txSigner, +\t\tsdk.WithBlockchainRPC(11155111, os.Getenv("RPC_URL")), +\t) +\tif err != nil { log.Fatal(err) } +\tdefer client.Close() + +\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) +\tdefer cancel() + +\tmyAddr := client.GetUserAddress() +\tpeer := os.Getenv("PEER_ADDRESS") + +\t// Fund the home channel before moving funds into the app session +\tif _, err = client.Deposit(ctx, 11155111, "usdc", decimal.NewFromInt(20)); err != nil { log.Fatal(err) } +\ttxHash, err := client.Checkpoint(ctx, "usdc") +\tif err != nil { log.Fatal(err) } +\tlog.Printf("Home channel funded with 20 USDC, tx: %s", txHash) + +\tdef := app.AppDefinitionV1{ +\t\tApplicationID: "my-app", +\t\tParticipants: []app.AppParticipantV1{ +\t\t\t{WalletAddress: myAddr, SignatureWeight: 50}, +\t\t\t{WalletAddress: peer, SignatureWeight: 50}, +\t\t}, +\t\tQuorum: 100, +\t\tNonce: 1, +\t} + +\tquorumSigs := []string{"0xMySig...", "0xPeerSig..."} +\tsessionID, versionStr, _, err := client.CreateAppSession(ctx, def, "{}", quorumSigs) +\tif err != nil { log.Fatal(err) } +\tlog.Printf("Session created: %s", sessionID) + +\tinitVersion, err := strconv.ParseUint(versionStr, 10, 64) +\tif err != nil { log.Fatal(err) } + +\tdepositUpdate := app.AppStateUpdateV1{ +\t\tAppSessionID: sessionID, +\t\tIntent: app.AppStateUpdateIntentDeposit, +\t\tVersion: initVersion + 1, +\t\tAllocations: []app.AppAllocationV1{ +\t\t\t{Participant: myAddr, Asset: "usdc", Amount: decimal.NewFromInt(12)}, +\t\t\t{Participant: peer, Asset: "usdc", Amount: decimal.NewFromInt(8)}, +\t\t}, +\t\tSessionData: "{}", +\t} +\toperateSigs := []string{"0xMySig...", "0xPeerSig..."} +\tif _, err := client.SubmitAppSessionDeposit(ctx, depositUpdate, operateSigs, "usdc", decimal.NewFromInt(20)); err != nil { log.Fatal(err) } +\tlog.Println("Session funded with 20 USDC") + +\tupdate := app.AppStateUpdateV1{ +\t\tAppSessionID: sessionID, +\t\tIntent: app.AppStateUpdateIntentOperate, +\t\tVersion: initVersion + 2, +\t\tAllocations: depositUpdate.Allocations, +\t\tSessionData: "{\"round\":1,\"winner\":\"me\"}", +\t} +\tif err := client.SubmitAppState(ctx, update, operateSigs); err != nil { log.Fatal(err) } +\tlog.Println("State updated") + +\tupdate.Intent = app.AppStateUpdateIntentClose +\tupdate.Version = initVersion + 3 +\tcloseSigs := []string{"0xMyCloseSig...", "0xPeerCloseSig..."} +\tif err := client.SubmitAppState(ctx, update, closeSigs); err != nil { log.Fatal(err) } +\tlog.Println("Session closed") +} +`; + +const GO_SCAFFOLD_AI_AGENT = `package main + +import ( +\t"context" +\t"log" +\t"os" +\t"os/signal" +\t"syscall" +\t"time" + +\t"github.com/layer-3/nitrolite/pkg/sign" +\tsdk "github.com/layer-3/nitrolite/sdk/go" +\t"github.com/shopspring/decimal" +) + +func main() { +\tstateSigner, err := sign.NewEthereumMsgSigner(os.Getenv("AGENT_PRIVATE_KEY")) +\tif err != nil { log.Fatal(err) } +\ttxSigner, err := sign.NewEthereumRawSigner(os.Getenv("AGENT_PRIVATE_KEY")) +\tif err != nil { log.Fatal(err) } + +\tclient, err := sdk.NewClient(os.Getenv("NITRONODE_URL"), stateSigner, txSigner, +\t\tsdk.WithBlockchainRPC(11155111, os.Getenv("RPC_URL")), +\t) +\tif err != nil { log.Fatal(err) } + +\tsigCh := make(chan os.Signal, 1) +\tsignal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) +\tgo func() { +\t\t<-sigCh +\t\tlog.Println("Shutting down agent...") +\t\tclient.Close() +\t}() + +\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) +\tdefer cancel() + +\t_, err = client.Deposit(ctx, 11155111, "usdc", decimal.NewFromInt(50)) +\tif err != nil { log.Fatal(err) } +\ttxHash, err := client.Checkpoint(ctx, "usdc") +\tif err != nil { log.Fatal(err) } +\tlog.Printf("Agent funded with 50 USDC, tx: %s", txHash) + +\trecipients := []string{"0x1111...", "0x2222..."} +\tfor _, r := range recipients { +\t\t_, err := client.Transfer(ctx, r, "usdc", decimal.NewFromFloat(0.10)) +\t\tif err != nil { +\t\t\tlog.Printf("Payment to %s failed: %v", r, err) +\t\t\tcontinue +\t\t} +\t\tlog.Printf("Paid 0.10 USDC to %s", r) +\t} + +\tlog.Println("Agent payments complete") +\t<-client.WaitCh() +} +`; + +// --------------------------------------------------------------------------- +// Initialize +// --------------------------------------------------------------------------- + +const releaseMetadata = readJsonFile(resolve(CONTENT_ROOT, 'release.json')); +const contentManifest = readJsonFile(resolve(CONTENT_ROOT, 'manifest.json')); + +if (!MONOREPO_SOURCE_AVAILABLE && !releaseMetadata) { + throw new Error('Packaged Yellow SDK MCP content is missing content/release.json. Reinstall @yellow-org/sdk-mcp from a published release.'); +} + +loadClientMethods(); +loadTypes(); +loadCompatExports(); +loadProtocolDocs(); +loadTerminology(); +loadV1API(); +loadGoTypes(); +loadGoSdkMethods(); + +// --------------------------------------------------------------------------- +// MCP Server +// --------------------------------------------------------------------------- + +const serverPackage = releaseMetadata?.serverPackage ?? '@yellow-org/sdk-mcp'; +const mcpName = releaseMetadata?.mcpName ?? 'io.github.layer-3/yellow-sdk-mcp'; +const serverVersion = readPackageVersion(resolve(PACKAGE_ROOT, 'package.json')) ?? releaseMetadata?.serverVersion ?? 'unknown'; +const sdkPackage = releaseMetadata?.sdkPackage ?? '@yellow-org/sdk'; +const sdkVersion = releaseMetadata?.sdkVersion ?? readPackageVersion(resolve(SDK_ROOT, 'package.json')) ?? 'unknown'; +const compatPackage = releaseMetadata?.compatPackage ?? '@yellow-org/sdk-compat'; +const compatVersion = releaseMetadata?.compatVersion ?? readPackageVersion(resolve(COMPAT_ROOT, 'package.json')) ?? 'unknown'; +const goModule = releaseMetadata?.goModule ?? GO_MODULE_PATH; +const goModuleVersion = releaseMetadata?.goModuleVersion ?? deriveGoModuleVersion(serverVersion); +const runtimeContentMode = MONOREPO_SOURCE_AVAILABLE ? 'source-tree' : releaseMetadata?.contentMode ?? 'packaged-snapshot'; +const versionPolicy = releaseMetadata?.versionPolicy ?? 'strict-mirror'; +const scaffoldSdkVersion = sdkVersion === 'unknown' ? (serverVersion === 'unknown' ? '^1' : serverVersion) : sdkVersion; + +const server = new McpServer({ + name: SERVER_NAME, + version: serverVersion, +}); + +// ========================== RESOURCES ====================================== + +server.resource('api-methods', 'nitrolite://api/methods', async () => { + const grouped: Record = {}; + for (const m of methods) { + (grouped[m.category] ??= []).push(m); + } + let text = '# Nitrolite SDK — Client Methods\n\n'; + for (const [cat, ms] of Object.entries(grouped).sort()) { + text += `## ${cat}\n\n`; + for (const m of ms) { + text += `### \`${m.signature}\`\n${m.description}\n\n`; + } + } + return { contents: [{ uri: 'nitrolite://api/methods', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('api-types', 'nitrolite://api/types', async () => { + const interfaces = types.filter(t => t.kind === 'interface'); + const aliases = types.filter(t => t.kind === 'type'); + let text = '# Nitrolite SDK — Types & Interfaces\n\n'; + text += `## Interfaces (${interfaces.length})\n\n`; + for (const t of interfaces) { + text += `### \`${t.name}\` (${t.source})\n\`\`\`typescript\n${t.fields}\n\`\`\`\n\n`; + } + text += `## Type Aliases (${aliases.length})\n\n`; + for (const t of aliases) { + text += `- \`${t.name}\` = \`${t.fields}\` (${t.source})\n`; + } + return { contents: [{ uri: 'nitrolite://api/types', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('api-enums', 'nitrolite://api/enums', async () => { + const enums = types.filter(t => t.kind === 'enum'); + let text = '# Nitrolite SDK — Enums\n\n'; + for (const e of enums) { + text += `## \`${e.name}\` (${e.source})\n\`\`\`typescript\n${e.fields}\n\`\`\`\n\n`; + } + return { contents: [{ uri: 'nitrolite://api/enums', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('examples-channels', 'nitrolite://examples/channels', async () => { + const text = `# Nitrolite SDK — Channel Examples + +## Creating a Channel & Depositing + +\`\`\`typescript +import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +const { stateSigner, txSigner } = createSigners('0xYourPrivateKey...'); +const client = await Client.create( + 'wss://nitronode.example.com/ws', + stateSigner, + txSigner, + withBlockchainRPC(80002n, 'https://rpc.amoy.polygon.technology'), +); + +// Deposit creates channel if needed +const state = await client.deposit(80002n, 'usdc', new Decimal(10)); +const txHash = await client.checkpoint('usdc'); +console.log('Deposit on-chain tx:', txHash); +\`\`\` + +## Querying Channels + +\`\`\`typescript +const userAddress = client.getUserAddress(); +const { channels } = await client.getChannels(userAddress); +for (const ch of channels) { + console.log(ch.channelId, ch.status); +} +\`\`\` + +## Closing a Channel + +\`\`\`typescript +// closeHomeChannel prepares the finalize state — checkpoint submits it on-chain +const finalState = await client.closeHomeChannel('usdc'); +const closeTx = await client.checkpoint('usdc'); +console.log('Channel closed, tx:', closeTx); +\`\`\` +`; + return { contents: [{ uri: 'nitrolite://examples/channels', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('examples-transfers', 'nitrolite://examples/transfers', async () => { + const text = `# Nitrolite SDK — Transfer Examples + +## Simple Transfer + +\`\`\`typescript +import Decimal from 'decimal.js'; + +const state = await client.transfer('0xRecipient...', 'usdc', new Decimal('5.0')); +console.log('Transfer tx ID:', state.transition.txId); +\`\`\` + +## Using the Compat Layer + +\`\`\`typescript +import { NitroliteClient } from '@yellow-org/sdk-compat'; + +const client = await NitroliteClient.create({ + wsURL: 'wss://nitronode.example.com/ws', + walletClient, + chainId: 11155111, + blockchainRPCs: { 11155111: 'https://rpc.sepolia.org' }, +}); + +await client.transfer('0xRecipient...', [{ asset: 'usdc', amount: '5.0' }]); +\`\`\` +`; + return { contents: [{ uri: 'nitrolite://examples/transfers', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('examples-app-sessions', 'nitrolite://examples/app-sessions', async () => { + const text = `# Nitrolite SDK — App Session Examples + +## Creating an App Session + +\`\`\`typescript +import { app } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +// Assumes client and CHAIN_ID are already defined. +await client.deposit(CHAIN_ID, 'usdc', new Decimal(20)); +await client.checkpoint('usdc'); + +// 1. Define the app session +const definition: app.AppDefinitionV1 = { + applicationId: 'my-game-app', + participants: [ + { walletAddress: '0xAlice...', signatureWeight: 50 }, + { walletAddress: '0xBob...', signatureWeight: 50 }, + ], + quorum: 100, // Both must agree + nonce: BigInt(Date.now()), +}; + +// 2. Collect quorum signatures from participants (off-band) +const quorumSigs: string[] = ['0xAliceSig...', '0xBobSig...']; + +// 3. Create the session +const result = await client.createAppSession(definition, '{}', quorumSigs); +console.log('Created session:', result.appSessionId); + +// 4. Fund the session before submitting non-zero allocations +const depositUpdate: app.AppStateUpdateV1 = { + appSessionId: result.appSessionId, + intent: app.AppStateUpdateIntent.Deposit, + version: 2n, + allocations: [ + { participant: '0xAlice...', asset: 'usdc', amount: new Decimal(15) }, + { participant: '0xBob...', asset: 'usdc', amount: new Decimal(5) }, + ], + sessionData: '{}', +}; +await client.submitAppSessionDeposit(depositUpdate, ['0xAliceSig...', '0xBobSig...'], 'usdc', new Decimal(20)); +\`\`\` + +## Submitting App State + +\`\`\`typescript +import Decimal from 'decimal.js'; + +const appUpdate: app.AppStateUpdateV1 = { + appSessionId: result.appSessionId, + intent: app.AppStateUpdateIntent.Operate, + version: 3n, + allocations: [ + { participant: '0xAlice...', asset: 'usdc', amount: new Decimal(15) }, + { participant: '0xBob...', asset: 'usdc', amount: new Decimal(5) }, + ], + sessionData: '{"move": "e4"}', +}; +await client.submitAppState(appUpdate, ['0xAliceSig...', '0xBobSig...']); +\`\`\` + +> **Note:** There is no \`closeAppSession()\` on the SDK Client. To close a session, +> submit a state update with \`intent: app.AppStateUpdateIntent.Close\`. +`; + return { contents: [{ uri: 'nitrolite://examples/app-sessions', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('examples-auth', 'nitrolite://examples/auth', async () => { + const text = `# Nitrolite SDK — Authentication Examples + +## Compat Layer Auth Flow (Legacy v0.5.3 Pattern) + +\`\`\`typescript +import { + createAuthRequestMessage, + createAuthVerifyMessage, + createEIP712AuthMessageSigner, + parseAnyRPCResponse, + type AuthRequestParams, +} from '@yellow-org/sdk-compat'; + +// 1. Create auth request +const authParams: AuthRequestParams = { + address: account.address, + session_key: '0x0000000000000000000000000000000000000000', + application: 'My App', + expires_at: BigInt(Math.floor(Date.now() / 1000) + 3600), + scope: 'app.create', + allowances: [{ asset: 'usdc', amount: '100.0' }], +}; +const authMsg = await createAuthRequestMessage(authParams); +ws.send(authMsg); + +// 2. Parse challenge +const challengeRaw = await waitForResponse(); +const challenge = parseAnyRPCResponse(challengeRaw); + +// 3. Create EIP-712 signer and verify +const signer = createEIP712AuthMessageSigner( + walletClient, + { scope: authParams.scope, session_key: authParams.session_key as \\\`0x\${string}\\\`, expires_at: authParams.expires_at, allowances: authParams.allowances }, + { name: 'Yellow Network' }, +); +const verifyMsg = await createAuthVerifyMessage(signer, challenge); +ws.send(verifyMsg); + +// 4. Parse verification result +const verifyRaw = await waitForResponse(); +const result = parseAnyRPCResponse(verifyRaw); +console.log('Authenticated:', result); +\`\`\` +`; + return { contents: [{ uri: 'nitrolite://examples/auth', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('migration-overview', 'nitrolite://migration/overview', async () => { + const content = readFile(resolve(COMPAT_ROOT, 'docs/migration-overview.md')); + const text = content || '# Migration Overview\n\nNo migration docs found. Check sdk/ts-compat/docs/.'; + return { contents: [{ uri: 'nitrolite://migration/overview', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('migration-nitronode', 'nitrolite://migration/nitronode', async () => { + const content = readFile(resolve(REPO_ROOT, 'MIGRATION-NITRONODE.md')); + const text = + content || + '# Clearnode -> Nitronode rename\n\nThe `clearnode` service was renamed to `nitronode` after v1.2.0. See MIGRATION-NITRONODE.md at the repo root for the full mapping (image names, env vars, metric prefixes, default URLs).'; + return { contents: [{ uri: 'nitrolite://migration/nitronode', text, mimeType: 'text/markdown' }] }; +}); + +// ========================== PROTOCOL RESOURCES ============================== + +server.resource('protocol-overview', 'nitrolite://protocol/overview', async () => { + const text = protocolDocs['overview'] || '# Protocol Overview\n\nProtocol docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/overview', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-terminology', 'nitrolite://protocol/terminology', async () => { + const text = protocolDocs['terminology'] || '# Terminology\n\nTerminology docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/terminology', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-wire-format', 'nitrolite://protocol/wire-format', async () => { + const text = protocolDocs['interactions'] || '# Wire Format\n\nInteraction docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/wire-format', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-rpc-methods', 'nitrolite://protocol/rpc-methods', async () => { + let text = '# V1 RPC Methods\n\nAll v1 RPC methods defined in `docs/api.yaml`. Methods use grouped naming: `{group}.v1.{method}`.\n\n'; + + // Group methods by their API group + const grouped: Record = {}; + for (const doc of rpcMethodDocs.values()) { + (grouped[doc.group] ??= []).push(doc); + } + for (const [group, docs] of Object.entries(grouped)) { + text += `## ${group}\n\n`; + text += '| Method | Description | Request Fields | Response Fields |\n|---|---|---|---|\n'; + for (const doc of docs) { + text += `| \`${doc.method}\` | ${doc.description} | ${doc.requestFields} | ${doc.responseFields} |\n`; + } + text += '\n'; + } + + text += '## Message Format\n\n'; + text += 'All messages use compact ordered arrays:\n\n'; + text += '**Request:** `{ "req": [REQUEST_ID, METHOD, PARAMS, TIMESTAMP], "sig": ["SIGNATURE"] }`\n\n'; + text += '**Response:** `{ "res": [REQUEST_ID, METHOD, DATA, TIMESTAMP], "sig": ["SIGNATURE"] }`\n\n'; + text += '**With App Session:** Add `"sid": "APP_SESSION_ID"` to route messages to app session participants.\n'; + return { contents: [{ uri: 'nitrolite://protocol/rpc-methods', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-cryptography', 'nitrolite://protocol/cryptography', async () => { + const text = protocolDocs['cryptography'] || '# Cryptography\n\nCryptography docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/cryptography', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-channel-lifecycle', 'nitrolite://protocol/channel-lifecycle', async () => { + const text = protocolDocs['channel-protocol'] || '# Channel Protocol\n\nChannel protocol docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/channel-lifecycle', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-state-model', 'nitrolite://protocol/state-model', async () => { + const text = protocolDocs['state-model'] || '# State Model\n\nState model docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/state-model', text, mimeType: 'text/markdown' }] }; +}); + +// ========================== SECURITY RESOURCES ============================== + +server.resource('security-overview', 'nitrolite://security/overview', async () => { + const text = protocolDocs['security-and-limitations'] || '# Security\n\nSecurity docs not found.'; + return { contents: [{ uri: 'nitrolite://security/overview', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('security-app-session-patterns', 'nitrolite://security/app-session-patterns', async () => { + const text = `# App Session Security Patterns + +Best practices for building secure, decentralization-ready app sessions on Nitrolite. + +## Quorum Design + +App sessions use a weight-based quorum system for governance: + +\`\`\`typescript +interface AppDefinitionV1 { + applicationId: string; + participants: AppParticipantV1[]; // each has walletAddress + signatureWeight + quorum: number; // minimum total weight to authorize actions (uint8) + nonce: bigint; +} +\`\`\` + +### Recommended Patterns + +**Equal 2-of-2 (peer-to-peer):** Both participants must agree. +\`\`\`json +{ "participants": [{ "signatureWeight": 50 }, { "signatureWeight": 50 }], "quorum": 100 } +\`\`\` + +**3-of-3 (multi-party unanimous):** All three must agree. +\`\`\`json +{ "participants": [{ "signatureWeight": 34 }, { "signatureWeight": 33 }, { "signatureWeight": 33 }], "quorum": 100 } +\`\`\` + +**2-of-3 with arbitrator:** Any two can authorize (third party can break ties). +\`\`\`json +{ "participants": [{ "signatureWeight": 50 }, { "signatureWeight": 50 }, { "signatureWeight": 50 }], "quorum": 100 } +\`\`\` + +**Weighted (operator-controlled):** One party has majority weight. +\`\`\`json +{ "participants": [{ "signatureWeight": 70 }, { "signatureWeight": 30 }], "quorum": 70 } +\`\`\` + +## Challenge Periods + +The challenge duration defines how long a dispute can be contested on-chain: +- **Short (1 hour):** For low-value, high-frequency operations +- **Medium (24 hours):** Recommended default for most applications +- **Long (7 days):** For high-value operations requiring more security + +## State Invariants + +Developers MUST ensure these invariants hold in every state update: +1. **Fund conservation:** Total allocations across participants MUST equal the committed amount +2. **Version ordering:** Each state version MUST be exactly previous + 1 +3. **Signature requirements:** Updates require signatures meeting the quorum threshold +4. **Non-negative allocations:** No participant's allocation can go below zero + +## Decentralization-Ready Patterns + +Even if not fully decentralized today, build app sessions so they would work in a decentralized environment: + +1. **Never trust a single party** — Use quorum >= total weight of any single participant +2. **Use challenge periods** — They exist to protect against malicious state submissions +3. **Keep state deterministic** — All participants should be able to independently verify state transitions +4. **Support unilateral enforcement** — Any participant should be able to enforce the latest agreed state on-chain +5. **Separate signing from logic** — Use session keys with spending caps rather than raw wallet keys + +## On-Chain Enforcement + +If off-chain cooperation fails, any participant can: +1. Submit the latest mutually signed state to the blockchain +2. The blockchain validates signatures, version ordering, and ledger invariants +3. After the challenge period, the state becomes final +4. Assets are distributed according to the final allocations +`; + return { contents: [{ uri: 'nitrolite://security/app-session-patterns', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('security-state-invariants', 'nitrolite://security/state-invariants', async () => { + const text = `# State Invariants + +Critical invariants that MUST hold across all state transitions. Violating these will cause on-chain enforcement to fail. + +## Ledger Invariant (Fund Conservation) + +\`\`\` +UserAllocation + NodeAllocation == UserNetFlow + NodeNetFlow +\`\`\` + +This ensures no assets can be created or destroyed through state transitions. The total distributable balance always equals the total cumulative flows. + +## Allocation Non-Negativity + +All allocation values (UserAllocation, NodeAllocation) MUST be non-negative. Net flow values MAY be negative (outbound transfers exceeding inbound). + +## Version Ordering + +- **Off-chain:** Each new version MUST equal previous version + 1 +- **On-chain enforcement:** Submitted version MUST be strictly greater than the current on-chain version + +## Signature Requirements + +- **Mutually signed states** (both user + node signatures) are the only states enforceable on-chain +- **Node-issued pending states** (node signature only) are NOT enforceable — they become enforceable after user acknowledgement +- Signature validation modes MUST be among the channel's approved validators + +## Channel Binding + +The channel identifier in every state MUST match the channel definition. This is verified both off-chain and on-chain. + +## Locked Funds + +Unless the channel is being closed: +\`\`\` +UserAllocation + NodeAllocation == LockedFunds +\`\`\` + +Locked funds MUST never be negative. The node MUST have sufficient vault funds when required to lock additional assets. + +## Empty Non-Home Ledger + +For non-cross-chain operations, the non-home ledger MUST be fully zeroed (all fields set to 0/zero-address). +`; + return { contents: [{ uri: 'nitrolite://security/state-invariants', text, mimeType: 'text/markdown' }] }; +}); + +// ========================== USE CASES RESOURCES ============================= + +server.resource('use-cases', 'nitrolite://use-cases', async () => { + const text = `# Nitrolite Use Cases + +What you can build with the Nitrolite SDK and state channels. + +## Peer-to-Peer Payments +Instant, gas-free token transfers between users. Open a channel, transfer any amount instantly, settle on-chain only when needed. +**SDK methods:** \`client.deposit()\`, \`client.transfer()\`, \`client.closeHomeChannel()\` + +## Gaming (Real-Time Wagering) +Turn-based or real-time games where players wager tokens. App sessions track game state; winners receive payouts automatically. +**SDK methods:** \`client.createAppSession()\`, \`client.submitAppSessionDeposit()\`, \`client.submitAppState()\` (close via \`submitAppState\` with Close intent) +**Example:** Yetris — a Tetris-style game with token wagering built on app sessions. + +## Multi-Party Checkout / Escrow +Multiple parties contribute to a shared pool (e.g., group payment, crowdfunding). Funds release when quorum conditions are met. +**SDK methods:** \`client.createAppSession()\`, \`client.submitAppSessionDeposit()\`, custom quorum weights, close via \`client.submitAppState()\` with Close intent +**Example:** Cosign Demo — a multi-party co-signing checkout flow. + +## AI Agent Payments +Autonomous AI agents making and receiving payments via state channels. Agents manage their own wallets, open channels, and transact programmatically. +**SDK methods:** \`Client.create()\`, \`client.deposit()\`, \`client.transfer()\` +**See also:** \`nitrolite://use-cases/ai-agents\` + +## DeFi Escrow & Atomic Swaps +Trustless exchange of assets between parties using escrow transitions. Cross-chain support via the unified asset model. +**SDK methods:** Escrow transitions via \`client.submitAppSessionDeposit()\` + +## Streaming Payments +Continuous micro-transfers over time (e.g., pay-per-second for compute, bandwidth, or content). State channels make sub-cent payments feasible. +**SDK methods:** \`client.transfer()\` in a loop with small amounts + +## Cross-Chain Operations +Move assets between blockchains through the escrow mechanism. Deposit on chain A, use on chain B, withdraw on chain C. +**SDK methods:** Cross-chain escrow transitions, \`client.deposit()\` on any supported chain +`; + return { contents: [{ uri: 'nitrolite://use-cases', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('use-cases-ai-agents', 'nitrolite://use-cases/ai-agents', async () => { + const text = `# AI Agent Use Cases + +How to use Nitrolite for AI agent payments and agent-to-agent interactions. + +## Why State Channels for AI Agents? + +AI agents need to make frequent, small payments — often thousands per session. On-chain transactions are too slow and expensive. State channels provide: +- **Instant finality** — no waiting for block confirmations +- **Near-zero cost** — gas only on channel open/close, not per-transfer +- **Programmable** — agents manage channels autonomously via the SDK + +## Agent Wallet Setup + +\`\`\`typescript +import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; + +// Agent has its own private key +const { stateSigner, txSigner } = createSigners(AGENT_PRIVATE_KEY); + +const client = await Client.create( + 'wss://nitronode.example.com/ws', + stateSigner, + txSigner, + withBlockchainRPC(chainId, RPC_URL), +); +\`\`\` + +## Agent-to-Agent Payments + +Two AI agents can transact directly through state channels: +1. Both agents open channels with the same nitronode +2. Agent A calls \`client.transfer(agentB_address, 'usdc', new Decimal('0.01'))\` +3. Agent B receives the transfer instantly +4. No on-chain transactions needed + +## Session Key Delegation + +For security, agents can use delegated session keys with spending caps: +- The agent's main wallet authorizes a session key during authentication +- The session key has a maximum spending allowance (e.g., 100 USDC) +- Once the cap is reached, the session key is revoked +- The main wallet funds are never at risk beyond the allowance + +## Autonomous Escrow + +AI agents can participate in app sessions for complex multi-step workflows: +1. Agent creates an app session with another agent or user +2. Both commit funds to the session +3. The application logic determines final allocations +4. The session closes and funds are distributed + +## Integration with Agent Frameworks + +The SDK works with any agent framework (LangChain, AutoGPT, CrewAI, etc.): +- Wrap SDK methods as agent tools +- Let the agent decide when to make payments +- Use session keys for safe autonomous operation + +## yao.com Proxy Pattern + +For agents that need a unified interface, yao.com provides a proxy layer: +- Agents connect to yao.com instead of directly to a nitronode +- yao.com handles channel management and routing +- Agents focus on their application logic +`; + return { contents: [{ uri: 'nitrolite://use-cases/ai-agents', text, mimeType: 'text/markdown' }] }; +}); + +// ========================== FULL EXAMPLE RESOURCES =========================== + +server.resource('examples-full-transfer', 'nitrolite://examples/full-transfer-script', async () => { + const text = `# Complete Transfer Script + +A fully working TypeScript script that connects to a nitronode, opens a channel, deposits funds, transfers tokens, and closes the channel. + +\`\`\`typescript +import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +// --- Configuration --- +const PRIVATE_KEY = process.env.PRIVATE_KEY as \`0x\${string}\`; +const NITRONODE_URL = process.env.NITRONODE_URL || 'wss://nitronode.example.com/ws'; +const RPC_URL = process.env.RPC_URL || 'https://rpc.sepolia.org'; +const CHAIN_ID = 80002n; // Polygon Amoy +const RECIPIENT = process.env.RECIPIENT as \`0x\${string}\`; + +async function main() { + // 1. Create signers from private key + const { stateSigner, txSigner } = createSigners(PRIVATE_KEY); + + // 2. Create SDK client — connects WebSocket + authenticates + const client = await Client.create( + NITRONODE_URL, + stateSigner, + txSigner, + withBlockchainRPC(CHAIN_ID, RPC_URL), + ); + console.log('Connected and authenticated'); + + // 3. Approve token spending (one-time per token, or when increasing allowance) + await client.approveToken(CHAIN_ID, 'usdc', new Decimal(1000)); + console.log('Token approved'); + + // 4. Deposit — creates channel if needed, then checkpoint on-chain + const depositState = await client.deposit(CHAIN_ID, 'usdc', new Decimal(10)); + const depositTx = await client.checkpoint('usdc'); + console.log('Deposited 10 USDC, tx:', depositTx); + + // 5. Transfer to recipient + const transferState = await client.transfer(RECIPIENT, 'usdc', new Decimal(5)); + console.log('Transferred 5 USDC, state version:', transferState.version); + + // 6. Check balances + const userAddress = client.getUserAddress(); + const balances = await client.getBalances(userAddress); + console.log('Current balances:', balances); + + // 7. Close channel — two steps: prepare finalize state, then checkpoint on-chain + const finalState = await client.closeHomeChannel('usdc'); + const closeTx = await client.checkpoint('usdc'); + console.log('Channel closed, tx:', closeTx); +} + +main().catch(console.error); +\`\`\` + +## Environment Variables + +- \`PRIVATE_KEY\` — Your wallet private key (hex with 0x prefix) +- \`NITRONODE_URL\` — WebSocket URL of the nitronode +- \`RPC_URL\` — Ethereum RPC endpoint for the target chain +- \`RECIPIENT\` — Address to transfer tokens to + +## Dependencies + +\`\`\`json +{ + "@yellow-org/sdk": "${scaffoldSdkVersion}", + "decimal.js": "^10.4.0", + "viem": "^2.46.0" +} +\`\`\` +`; + return { contents: [{ uri: 'nitrolite://examples/full-transfer-script', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('examples-full-app-session', 'nitrolite://examples/full-app-session-script', async () => { + const text = `# Complete App Session Script + +A fully working TypeScript script that creates a multi-party app session, submits state updates, and closes with final allocations. + +\`\`\`typescript +import { Client, createSigners, withBlockchainRPC, app } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +// --- Configuration --- +const PRIVATE_KEY = process.env.PRIVATE_KEY as \`0x\${string}\`; +const NITRONODE_URL = process.env.NITRONODE_URL || 'wss://nitronode.example.com/ws'; +const RPC_URL = process.env.RPC_URL || 'https://rpc.sepolia.org'; +const CHAIN_ID = 80002n; +const PEER_ADDRESS = process.env.PEER_ADDRESS as \`0x\${string}\`; + +async function main() { + const { stateSigner, txSigner } = createSigners(PRIVATE_KEY); + const myAddress = stateSigner.address; + + const client = await Client.create( + NITRONODE_URL, + stateSigner, + txSigner, + withBlockchainRPC(CHAIN_ID, RPC_URL), + ); + console.log('Connected'); + + // Ensure funds are available in the home channel before app-session funding + await client.deposit(CHAIN_ID, 'usdc', new Decimal(20)); + await client.checkpoint('usdc'); + + // 1. Define app session + const definition: app.AppDefinitionV1 = { + applicationId: 'my-game-app', + participants: [ + { walletAddress: myAddress, signatureWeight: 50 }, + { walletAddress: PEER_ADDRESS, signatureWeight: 50 }, + ], + quorum: 100, // Both must agree + nonce: BigInt(Date.now()), + }; + + // 2. Collect quorum signatures from participants (off-band signing) + const quorumSigs: string[] = ['0xMySignature...', '0xPeerSignature...']; + + // 3. Create app session + const session = await client.createAppSession(definition, '{}', quorumSigs); + console.log('App session created:', session.appSessionId); + + // 4. Fund the app session before submitting non-zero allocations + const depositUpdate: app.AppStateUpdateV1 = { + appSessionId: session.appSessionId, + intent: app.AppStateUpdateIntent.Deposit, + version: 2n, + allocations: [ + { participant: myAddress, asset: 'usdc', amount: new Decimal(15) }, + { participant: PEER_ADDRESS, asset: 'usdc', amount: new Decimal(5) }, + ], + sessionData: '{}', + }; + await client.submitAppSessionDeposit(depositUpdate, ['0xMySig...', '0xPeerSig...'], 'usdc', new Decimal(20)); + console.log('Session funded with 20 USDC'); + + // 5. Submit state update — e.g., after a game round + const appUpdate: app.AppStateUpdateV1 = { + appSessionId: session.appSessionId, + intent: app.AppStateUpdateIntent.Operate, + version: 3n, + allocations: depositUpdate.allocations, + sessionData: '{"round": 1, "winner": "me"}', + }; + await client.submitAppState(appUpdate, ['0xMySig...', '0xPeerSig...']); + console.log('State updated: I won 5 USDC'); + + // 6. Close app session — submit final state with Close intent + const closeUpdate: app.AppStateUpdateV1 = { + ...appUpdate, + intent: app.AppStateUpdateIntent.Close, + version: 4n, + }; + await client.submitAppState(closeUpdate, ['0xMyCloseSig...', '0xPeerCloseSig...']); + console.log('Session closed, funds returned to channels'); +} + +main().catch(console.error); +\`\`\` + +## Key Concepts + +- **Quorum:** Set to 100 with equal weights (50/50) — both parties must sign every state update +- **Allocations:** Must always sum to the total committed amount (fund conservation invariant) +- **Intent:** Use \`Operate\` for normal updates, \`Close\` for final settlement (there is no separate \`closeAppSession()\` method) +- **Session data:** Optional string field for app-specific metadata (game state, etc.) +- **Quorum sigs:** Participants sign the app state off-band; signatures are collected and submitted together + +## Dependencies + +\`\`\`json +{ + "@yellow-org/sdk": "${scaffoldSdkVersion}", + "viem": "^2.46.0", + "decimal.js": "^10.6.0" +} +\`\`\` +`; + return { contents: [{ uri: 'nitrolite://examples/full-app-session-script', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-enforcement', 'nitrolite://protocol/enforcement', async () => { + const text = protocolDocs['enforcement'] || '# Enforcement\n\nEnforcement docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/enforcement', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-cross-chain', 'nitrolite://protocol/cross-chain', async () => { + const text = protocolDocs['cross-chain-and-assets'] || '# Cross-Chain & Assets\n\nCross-chain docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/cross-chain', text, mimeType: 'text/markdown' }] }; +}); + +server.resource('protocol-interactions', 'nitrolite://protocol/interactions', async () => { + const text = protocolDocs['interactions'] || '# Interactions\n\nInteractions docs not found.'; + return { contents: [{ uri: 'nitrolite://protocol/interactions', text, mimeType: 'text/markdown' }] }; +}); + +// ========================== GO SDK RESOURCES ================================ + +server.resource('go-api-methods', 'nitrolite://go-api/methods', async () => ({ + contents: [{ uri: 'nitrolite://go-api/methods', text: buildGoApiMethodsContent(), mimeType: 'text/markdown' }], +})); + +server.resource('go-api-types', 'nitrolite://go-api/types', async () => ({ + contents: [{ uri: 'nitrolite://go-api/types', text: buildGoTypesContent(), mimeType: 'text/markdown' }], +})); + +server.resource('go-examples-full-transfer', 'nitrolite://go-examples/full-transfer-script', async () => ({ + contents: [{ uri: 'nitrolite://go-examples/full-transfer-script', text: GO_TRANSFER_EXAMPLE, mimeType: 'text/markdown' }], +})); + +server.resource('go-examples-full-app-session', 'nitrolite://go-examples/full-app-session-script', async () => ({ + contents: [{ uri: 'nitrolite://go-examples/full-app-session-script', text: GO_APP_SESSION_EXAMPLE, mimeType: 'text/markdown' }], +})); + +server.resource('protocol-auth-flow', 'nitrolite://protocol/auth-flow', async () => ({ + contents: [{ uri: 'nitrolite://protocol/auth-flow', text: AUTH_FLOW_CONTENT, mimeType: 'text/markdown' }], +})); + + +// ========================== TOOLS ========================================== + +function boundedToolString(description: string, max = 120) { + return z.string().trim().min(1).max(max).describe(description); +} + +server.tool( + 'server_info', + 'Return Yellow SDK MCP package, SDK, compat, and indexed content version information for debugging and support.', + {}, + async () => ({ + content: [{ + type: 'text' as const, + text: JSON.stringify({ + name: SERVER_NAME, + serverPackage, + mcpName, + version: serverVersion, + sdkPackage, + sdkVersion, + compatPackage, + compatVersion, + goModule, + goModuleVersion, + protocolVersion: 'v1', + transport: 'stdio', + contentMode: runtimeContentMode, + versionPolicy, + sourceCommit: releaseMetadata?.sourceCommit ?? 'unknown', + contentGeneratedAt: releaseMetadata?.generatedAt ?? 'unknown', + contentManifestFiles: contentManifest?.counts?.totalFiles ?? contentManifest?.files?.length ?? 0, + }, null, 2), + }], + }), +); + +server.tool( + 'lookup_method', + 'Look up a specific SDK Client method by name — returns signature, params, return type, usage context', + { + name: boundedToolString('Method name (e.g. "transfer", "deposit", "getChannels", "Transfer", "Deposit")'), + language: z.enum(['typescript', 'go', 'both']).optional().default('typescript').describe('SDK language to search: "typescript" (default), "go", or "both"'), + }, + async ({ name, language }) => { + const query = name.toLowerCase(); + const parts: string[] = []; + + if (language === 'typescript' || language === 'both') { + const tsMatches = methods.filter(m => m.name.toLowerCase().includes(query)); + if (tsMatches.length > 0) { + const header = language === 'both' ? '## TypeScript SDK\n\n' : ''; + parts.push(header + tsMatches.map(m => + `### ${m.name}\n**Signature:** \`${m.signature}\`\n**Category:** ${m.category}\n**Description:** ${m.description}` + ).join('\n\n---\n\n')); + } + } + + if (language === 'go' || language === 'both') { + const goMatches = goMethods.filter(m => m.name.toLowerCase().includes(query)); + if (goMatches.length > 0) { + const header = language === 'both' ? '## Go SDK\n\n' : ''; + parts.push(header + goMatches.map(m => + `### ${m.name}\n**Signature:**\n\`\`\`go\n${m.signature}\n\`\`\`\n**Category:** ${m.category}\n**Description:** ${m.comment}` + ).join('\n\n---\n\n')); + } + } + + if (parts.length === 0) { + return { content: [{ type: 'text' as const, text: `No method matching "${name}" found. Available categories: ${[...new Set(methods.map(m => m.category))].join(', ')}` }] }; + } + return { content: [{ type: 'text' as const, text: parts.join('\n\n') }] }; + }, +); + +server.tool( + 'lookup_type', + 'Look up a type, interface, or enum by name — returns fields and source location', + { + name: boundedToolString('Type name (e.g. "Channel", "State", "RPCMethod", "AppSessionV1", "ChannelStatus")'), + language: z.enum(['typescript', 'go', 'both']).optional().default('typescript').describe('SDK language to search: "typescript" (default), "go", or "both"'), + }, + async ({ name, language }) => { + const query = name.toLowerCase(); + const parts: string[] = []; + + if (language === 'typescript' || language === 'both') { + const tsMatches = types.filter(t => t.name.toLowerCase().includes(query)); + if (tsMatches.length > 0) { + const header = language === 'both' ? '## TypeScript SDK\n\n' : ''; + parts.push(header + tsMatches.map(t => + `### ${t.name} (${t.kind})\n**Source:** ${t.source}\n\`\`\`typescript\n${t.fields}\n\`\`\`` + ).join('\n\n---\n\n')); + } + } + + if (language === 'go' || language === 'both') { + const goMatches = goTypes.filter(t => t.name.toLowerCase().includes(query)); + if (goMatches.length > 0) { + const header = language === 'both' ? '## Go SDK\n\n' : ''; + parts.push(header + goMatches.map(t => { + if (t.kind === 'struct') { + return `### ${t.name} (struct)\n**Source:** ${t.source}\n\`\`\`go\ntype ${t.name} struct {\n${t.fields}\n}\n\`\`\``; + } else if (t.kind === 'enum') { + return `### ${t.name} (enum)\n**Source:** ${t.source}\n**Values:**\n${t.fields.split('\n').map(v => `- \`${v}\``).join('\n')}`; + } + return `### ${t.name} (${t.kind})\n**Source:** ${t.source}\n\`\`\`go\ntype ${t.name} ${t.fields}\n\`\`\``; + }).join('\n\n---\n\n')); + } + } + + if (parts.length === 0) { + return { content: [{ type: 'text' as const, text: `No type matching "${name}" found. ${types.length} TS types and ${goTypes.length} Go types indexed.` }] }; + } + return { content: [{ type: 'text' as const, text: parts.join('\n\n') }] }; + }, +); + +server.tool( + 'search_api', + 'Fuzzy search across all SDK methods and types', + { + query: boundedToolString('Search query (e.g. "session key", "balance", "transfer", "AppSession")', 160), + language: z.enum(['typescript', 'go', 'both']).optional().default('typescript').describe('SDK language to search: "typescript" (default), "go", or "both"'), + }, + async ({ query, language }) => { + const q = query.toLowerCase(); + let text = `# Search results for "${query}"\n\n`; + let totalHits = 0; + + if (language === 'typescript' || language === 'both') { + const methodHits = methods.filter(m => + m.name.toLowerCase().includes(q) || m.description.toLowerCase().includes(q) || m.category.toLowerCase().includes(q) + ); + const typeHits = types.filter(t => + t.name.toLowerCase().includes(q) || t.fields.toLowerCase().includes(q) + ); + const prefix = language === 'both' ? 'TypeScript SDK ' : ''; + if (methodHits.length > 0) { + text += `## ${prefix}Methods (${methodHits.length} matches)\n`; + for (const m of methodHits.slice(0, 10)) text += `- \`${m.signature}\` — ${m.category}\n`; + text += '\n'; + totalHits += methodHits.length; + } + if (typeHits.length > 0) { + text += `## ${prefix}Types (${typeHits.length} matches)\n`; + for (const t of typeHits.slice(0, 10)) text += `- \`${t.name}\` (${t.kind}) — ${t.source}\n`; + text += '\n'; + totalHits += typeHits.length; + } + } + + if (language === 'go' || language === 'both') { + const goMethodHits = goMethods.filter(m => + m.name.toLowerCase().includes(q) || m.comment.toLowerCase().includes(q) || m.category.toLowerCase().includes(q) + ); + const goTypeHits = goTypes.filter(t => + t.name.toLowerCase().includes(q) || t.fields.toLowerCase().includes(q) + ); + const prefix = language === 'both' ? 'Go SDK ' : ''; + if (goMethodHits.length > 0) { + text += `## ${prefix}Methods (${goMethodHits.length} matches)\n`; + for (const m of goMethodHits.slice(0, 10)) text += `- \`${m.name}\` — ${m.category}\n`; + text += '\n'; + totalHits += goMethodHits.length; + } + if (goTypeHits.length > 0) { + text += `## ${prefix}Types (${goTypeHits.length} matches)\n`; + for (const t of goTypeHits.slice(0, 10)) text += `- \`${t.name}\` (${t.kind}) — ${t.source}\n`; + text += '\n'; + totalHits += goTypeHits.length; + } + } + + if (totalHits === 0) text += 'No matches found. Try a broader term.\n'; + return { content: [{ type: 'text' as const, text }] }; + }, +); + +server.tool( + 'get_rpc_method', + 'Get the RPC wire format for a 0.5.x compat-layer method and its v1 equivalent. For v1 method reference, see docs/api.yaml.', + { method: boundedToolString('0.5.x compat method name (e.g. "get_channels", "transfer", "create_app_session")') }, + async ({ method }) => { + // NOTE: These are 0.5.x compat-layer method names mapped to their v1 wire equivalents. + // The v1 API uses grouped methods (e.g. channels.v1.submit_state). The canonical v1 + // reference is docs/api.yaml. This tool exists for sdk-compat integration test authors. + const rpcMethods: Record = { + ping: { wireMethod: 'node.v1.ping', reqFormat: '{ req: [requestId, "ping", {}, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "ping", { pong: true }] }' }, + get_channels: { wireMethod: 'channels.v1.get_channels', reqFormat: '{ req: [requestId, "get_channels", { wallet?, status?, asset?, channel_type?, pagination? }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "get_channels", { channels: [...], metadata: {...} }] }' }, + get_ledger_balances: { wireMethod: 'user.v1.get_balances', reqFormat: '{ req: [requestId, "get_ledger_balances", { wallet }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "get_ledger_balances", { balances: RPCBalance[] }] }' }, + transfer: { wireMethod: 'channels.v1.submit_state', reqFormat: '{ req: [requestId, "transfer", { destination, allocations: [{ asset, amount }] }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "transfer", { state }] }' }, + create_channel: { wireMethod: 'channels.v1.request_creation', reqFormat: '{ req: [requestId, "create_channel", [{ chain_id, token }], timestamp], sig: [...] }', resFormat: '{ res: [requestId, "create_channel", [{ channel_id, channel, state, server_signature }], timestamp], sig: [...] }' }, + close_channel: { wireMethod: 'channels.v1.submit_state', reqFormat: '{ req: [requestId, "close_channel", { channel_id, funds_destination }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "close_channel", { channel_id, state, server_signature }] }' }, + create_app_session: { wireMethod: 'app_sessions.v1.create_app_session', reqFormat: '{ req: [requestId, "create_app_session", { definition, session_data, quorum_sigs, owner_sig? }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "create_app_session", { app_session_id, version, status }] }' }, + submit_app_state: { wireMethod: 'app_sessions.v1.submit_app_state', reqFormat: '{ req: [requestId, "submit_app_state", { app_state_update, quorum_sigs }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "submit_app_state", { accepted: boolean }] }' }, + get_app_sessions: { wireMethod: 'app_sessions.v1.get_app_sessions', reqFormat: '{ req: [requestId, "get_app_sessions", { filters? }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "get_app_sessions", { sessions: AppSession[] }] }' }, + get_app_definition: { wireMethod: 'app_sessions.v1.get_app_definition', reqFormat: '{ req: [requestId, "get_app_definition", { app_session_id }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "get_app_definition", { definition }] }' }, + get_ledger_transactions: { wireMethod: 'user.v1.get_transactions', reqFormat: '{ req: [requestId, "get_ledger_transactions", { wallet, filters? }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "get_ledger_transactions", { transactions: RPCTransaction[] }] }' }, + resize_channel: { wireMethod: 'channels.v1.submit_state', reqFormat: '{ req: [requestId, "resize_channel", { channel_id, resize_amount, allocate_amount, funds_destination }, timestamp], sig: [...] }', resFormat: '{ res: [requestId, "resize_channel", { channel_id, state, server_signature }] }' }, + }; + + const key = method.toLowerCase(); + const info = rpcMethods[key]; + if (!info) { + return { content: [{ type: 'text' as const, text: `Unknown RPC method "${method}". Available: ${Object.keys(rpcMethods).join(', ')}` }] }; + } + const text = `## RPC: ${method}\n\n**V1 Wire Method:** \`${info.wireMethod}\`\n\n**Request format (v0.5.3 compat):**\n\`\`\`\n${info.reqFormat}\n\`\`\`\n\n**Response format:**\n\`\`\`\n${info.resFormat}\n\`\`\``; + return { content: [{ type: 'text' as const, text }] }; + }, +); + +server.tool( + 'validate_import', + 'Check if a symbol is exported from sdk-compat barrel — returns yes/no + correct import path', + { symbol: boundedToolString('Symbol name (e.g. "NitroliteClient", "RPCMethod", "createTransferMessage")') }, + async ({ symbol }) => { + const compatMatch = findNamedExport(compatExports, symbol); + if (compatMatch.found) { + return { content: [{ type: 'text' as const, text: `**${symbol}** is exported from \`@yellow-org/sdk-compat\`.\n\n\`\`\`typescript\n${renderImportStatement('@yellow-org/sdk-compat', symbol, compatMatch.isTypeOnly)}\n\`\`\`` }] }; + } + + // Check if it's in the main SDK + const sdkBarrelContent = readFile(resolve(SDK_ROOT, 'src/index.ts')); + const sdkExports = extractExports(sdkBarrelContent); + const sdkMatch = findNamedExport(sdkExports, symbol); + if (sdkMatch.found) { + return { content: [{ type: 'text' as const, text: `**${symbol}** is NOT in \`@yellow-org/sdk-compat\` but IS in \`@yellow-org/sdk\`.\n\n\`\`\`typescript\n${renderImportStatement('@yellow-org/sdk', symbol, sdkMatch.isTypeOnly)}\n\`\`\`\n\n> Note: SDK classes should not be re-exported from compat (SSR risk). Import directly from \`@yellow-org/sdk\`.` }] }; + } + + return { content: [{ type: 'text' as const, text: `**${symbol}** was not found in either \`@yellow-org/sdk-compat\` or \`@yellow-org/sdk\` barrel exports. It may be a deep import or may not exist.` }] }; + }, +); + +server.tool( + 'explain_concept', + 'Plain-English explanation of a Nitrolite protocol concept (e.g. "state channel", "app session", "challenge period")', + { concept: boundedToolString('Concept name (e.g. "state channel", "app session", "challenge period", "nitronode", "vault")') }, + async ({ concept }) => { + const query = concept.toLowerCase().trim(); + + // Direct match + const direct = concepts.get(query); + if (direct) { + return { content: [{ type: 'text' as const, text: direct }] }; + } + + // Fuzzy match — find concepts that contain the query or vice versa + const matches: string[] = []; + for (const [key, value] of concepts) { + if (key.includes(query) || query.includes(key)) { + matches.push(value); + } + } + if (matches.length > 0) { + return { content: [{ type: 'text' as const, text: matches.join('\n\n---\n\n') }] }; + } + + // Word-level fuzzy — match any word + const words = query.split(/\s+/); + for (const [key, value] of concepts) { + if (words.some(w => key.includes(w))) { + matches.push(value); + } + } + if (matches.length > 0) { + return { content: [{ type: 'text' as const, text: `No exact match for "${concept}". Related concepts:\n\n${matches.slice(0, 5).join('\n\n---\n\n')}` }] }; + } + + return { content: [{ type: 'text' as const, text: `No concept matching "${concept}" found. ${concepts.size} concepts indexed from protocol terminology. Try broader terms like "channel", "state", "session", "escrow", "transfer".` }] }; + }, +); + +server.tool( + 'lookup_rpc_method', + 'Look up a v1 RPC method from docs/api.yaml — returns description, request/response fields. Methods use grouped naming: {group}.v1.{method}', + { method: boundedToolString('V1 RPC method name or search term (e.g. "channels.v1.get_home_channel", "submit_state", "get_balances")') }, + async ({ method }) => { + const query = method.toLowerCase().trim(); + + // Direct match + const doc = rpcMethodDocs.get(query); + if (doc) { + let text = `## V1 RPC Method: \`${doc.method}\`\n\n`; + text += `**Group:** ${doc.group}\n**Description:** ${doc.description}\n\n`; + text += `**Request fields:** ${doc.requestFields}\n`; + text += `**Response fields:** ${doc.responseFields}\n`; + return { content: [{ type: 'text' as const, text }] }; + } + + // Fuzzy match — search in full method name and short name + const matches: RPCMethodDoc[] = []; + for (const [key, val] of rpcMethodDocs) { + const shortName = key.split('.').pop() || ''; + if (key.includes(query) || query.includes(shortName) || shortName.includes(query)) { + matches.push(val); + } + } + if (matches.length > 0) { + const text = matches.map(d => + `- \`${d.method}\` — ${d.description}` + ).join('\n'); + return { content: [{ type: 'text' as const, text: `Matching v1 RPC methods:\n\n${text}` }] }; + } + + return { content: [{ type: 'text' as const, text: `No v1 RPC method matching "${method}". Available methods:\n${[...rpcMethodDocs.keys()].join(', ')}` }] }; + }, +); + +server.tool( + 'scaffold_project', + 'Generate a starter project structure for a new Nitrolite app — TypeScript or Go templates', + { template: z.enum(['transfer-app', 'app-session', 'ai-agent', 'go-transfer-app', 'go-app-session', 'go-ai-agent']).describe('Project template: TypeScript (transfer-app, app-session, ai-agent) or Go (go-transfer-app, go-app-session, go-ai-agent)') }, + async ({ template }) => { + // Go templates — different output shape + if (template === 'go-transfer-app' || template === 'go-app-session' || template === 'go-ai-agent') { + const goTemplateMap: Record = { + 'go-transfer-app': GO_SCAFFOLD_TRANSFER, + 'go-app-session': GO_SCAFFOLD_APP_SESSION, + 'go-ai-agent': GO_SCAFFOLD_AI_AGENT, + }; + const baseName = template.replace('go-', ''); + const goMod = `module my-nitrolite-${baseName}\n\ngo 1.25.0\n\nrequire (\n\t${goModule} ${goModuleVersion}\n\tgithub.com/shopspring/decimal v1.4.0\n)`; + const envKey = template === 'go-ai-agent' ? 'AGENT_PRIVATE_KEY' : 'PRIVATE_KEY'; + const envExtra = template === 'go-transfer-app' ? '\nRECIPIENT=your_recipient_address' : template === 'go-app-session' ? '\nPEER_ADDRESS=peer_wallet_address' : ''; + const text = `# Scaffold: ${template}\n\n## go.mod\n\`\`\`\n${goMod}\n\`\`\`\n\n## main.go\n\`\`\`go\n${goTemplateMap[template]}\`\`\`\n\n## .env.example\n\`\`\`\n${envKey}=your_hex_key\nNITRONODE_URL=wss://nitronode.example.com/ws\nRPC_URL=https://rpc.sepolia.org${envExtra}\n\`\`\`\n\n## Setup\n\`\`\`bash\ngo mod tidy\ngo run .\n\`\`\``; + return { content: [{ type: 'text' as const, text }] }; + } + const packageJson = { + name: `nitrolite-${template}`, + version: '0.1.0', + private: true, + type: 'module', + scripts: { start: 'npx tsx src/index.ts', build: 'tsc', typecheck: 'tsc --noEmit' }, + dependencies: { + '@yellow-org/sdk': scaffoldSdkVersion, + 'decimal.js': '^10.4.0', + viem: '^2.46.0', + }, + devDependencies: { typescript: '^5.7.0', tsx: '^4.19.0', '@types/node': '^22.0.0' }, + }; + + const tsconfig = { + compilerOptions: { + target: 'es2020', module: 'ESNext', moduleResolution: 'bundler', + strict: true, esModuleInterop: true, outDir: 'dist', declaration: true, + }, + include: ['src'], + }; + + const templates: Record = { + 'transfer-app': `import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +const PRIVATE_KEY = process.env.PRIVATE_KEY as \`0x\$\{string}\`; +const NITRONODE_URL = process.env.NITRONODE_URL || 'wss://nitronode.example.com/ws'; +const RPC_URL = process.env.RPC_URL || 'https://rpc.sepolia.org'; +const CHAIN_ID = 80002n; + +async function main() { + const { stateSigner, txSigner } = createSigners(PRIVATE_KEY); + + const client = await Client.create(NITRONODE_URL, stateSigner, txSigner, withBlockchainRPC(CHAIN_ID, RPC_URL)); + console.log('Connected to nitronode'); + + // Deposit funds + await client.deposit(CHAIN_ID, 'usdc', new Decimal(10)); + await client.checkpoint('usdc'); + console.log('Deposited 10 USDC'); + + // Transfer + const recipient = process.env.RECIPIENT as \`0x\$\{string}\`; + await client.transfer(recipient, 'usdc', new Decimal(5)); + console.log('Transferred 5 USDC to', recipient); +} + +main().catch(console.error); +`, + 'app-session': `import { Client, createSigners, withBlockchainRPC, app } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +const PRIVATE_KEY = process.env.PRIVATE_KEY as \`0x\$\{string}\`; +const NITRONODE_URL = process.env.NITRONODE_URL || 'wss://nitronode.example.com/ws'; +const RPC_URL = process.env.RPC_URL || 'https://rpc.sepolia.org'; +const CHAIN_ID = 80002n; +const PEER = process.env.PEER_ADDRESS as \`0x\$\{string}\`; + +async function main() { + const { stateSigner, txSigner } = createSigners(PRIVATE_KEY); + const myAddress = stateSigner.address; + + const client = await Client.create(NITRONODE_URL, stateSigner, txSigner, withBlockchainRPC(CHAIN_ID, RPC_URL)); + + // Fund the home channel before moving funds into the app session + await client.deposit(CHAIN_ID, 'usdc', new Decimal(20)); + await client.checkpoint('usdc'); + + // Define app session + const definition: app.AppDefinitionV1 = { + applicationId: 'my-app', + participants: [ + { walletAddress: myAddress, signatureWeight: 50 }, + { walletAddress: PEER, signatureWeight: 50 }, + ], + quorum: 100, + nonce: BigInt(Date.now()), + }; + + // Collect quorum signatures from participants (off-band) + const quorumSigs: string[] = ['0xMySig...', '0xPeerSig...']; + + // Create app session + const session = await client.createAppSession(definition, '{}', quorumSigs); + console.log('Session created:', session.appSessionId); + + // Fund the app session before non-zero allocations + const depositUpdate: app.AppStateUpdateV1 = { + appSessionId: session.appSessionId, + intent: app.AppStateUpdateIntent.Deposit, + version: 2n, + allocations: [ + { participant: myAddress, asset: 'usdc', amount: new Decimal(12) }, + { participant: PEER, asset: 'usdc', amount: new Decimal(8) }, + ], + sessionData: '{}', + }; + await client.submitAppSessionDeposit(depositUpdate, ['0xMySig...', '0xPeerSig...'], 'usdc', new Decimal(20)); + console.log('Session funded with 20 USDC'); + + // Submit state update + const update: app.AppStateUpdateV1 = { + appSessionId: session.appSessionId, + intent: app.AppStateUpdateIntent.Operate, + version: 3n, + allocations: depositUpdate.allocations, + sessionData: '{}', + }; + await client.submitAppState(update, ['0xMySig...', '0xPeerSig...']); + + // Close session — submit with Close intent + const closeUpdate: app.AppStateUpdateV1 = { ...update, intent: app.AppStateUpdateIntent.Close, version: 4n }; + await client.submitAppState(closeUpdate, ['0xMyCloseSig...', '0xPeerCloseSig...']); + console.log('Session closed'); +} + +main().catch(console.error); +`, + 'ai-agent': `import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +const AGENT_KEY = process.env.AGENT_PRIVATE_KEY as \`0x\$\{string}\`; +const NITRONODE_URL = process.env.NITRONODE_URL || 'wss://nitronode.example.com/ws'; +const RPC_URL = process.env.RPC_URL || 'https://rpc.sepolia.org'; +const CHAIN_ID = 80002n; + +async function createAgentClient() { + const { stateSigner, txSigner } = createSigners(AGENT_KEY); + return Client.create(NITRONODE_URL, stateSigner, txSigner, withBlockchainRPC(CHAIN_ID, RPC_URL)); +} + +async function payForService(client: Awaited>, recipient: \`0x\$\{string}\`, amount: Decimal) { + const state = await client.transfer(recipient, 'usdc', amount); + console.log(\`Paid \$\{amount} USDC to \$\{recipient}, version: \$\{state.version}\`); + return state; +} + +async function main() { + const client = await createAgentClient(); + console.log('Agent connected to nitronode'); + + // Ensure the agent has funds + await client.deposit(CHAIN_ID, 'usdc', new Decimal(50)); + await client.checkpoint('usdc'); + + // Agent payment loop — pay for each task + const tasks = [ + { recipient: '0x1111111111111111111111111111111111111111' as \`0x\$\{string}\`, amount: new Decimal('0.10') }, + { recipient: '0x2222222222222222222222222222222222222222' as \`0x\$\{string}\`, amount: new Decimal('0.25') }, + ]; + + for (const task of tasks) { + await payForService(client, task.recipient, task.amount); + } + + console.log('All payments complete'); +} + +main().catch(console.error); +`, + }; + + const envExample = `${template === 'ai-agent' ? 'AGENT_PRIVATE_KEY' : 'PRIVATE_KEY'}=0x... +NITRONODE_URL=wss://nitronode.example.com/ws +RPC_URL=https://rpc.sepolia.org +${template === 'transfer-app' ? 'RECIPIENT=0x...' : ''}${template === 'app-session' ? 'PEER_ADDRESS=0x...' : ''}`; + + const text = `# Scaffold: ${template} + +## package.json +\`\`\`json +${JSON.stringify(packageJson, null, 2)} +\`\`\` + +## tsconfig.json +\`\`\`json +${JSON.stringify(tsconfig, null, 2)} +\`\`\` + +## src/index.ts +\`\`\`typescript +${templates[template]} +\`\`\` + +## .env.example +\`\`\` +${envExample} +\`\`\` + +## Setup +\`\`\`bash +npm install +cp .env.example .env # Fill in your values +npx tsx src/index.ts +\`\`\``; + + return { content: [{ type: 'text' as const, text }] }; + }, +); + +// ========================== PROMPTS ======================================== + +server.prompt( + 'create-channel-app', + 'Step-by-step guide to build an app using Nitrolite state channels', + async () => ({ + messages: [{ + role: 'user' as const, + content: { + type: 'text' as const, + text: `Guide me through building a Nitrolite state channel application. Cover: + +1. **Setup** — Install dependencies (@yellow-org/sdk, viem), create Client with config +2. **Authentication** — Connect wallet, establish WebSocket, authenticate with nitronode +3. **Channel Lifecycle** — Deposit (auto-creates channel), query channels, close channel +4. **Transfers** — Send tokens to another participant via state channels +5. **App Sessions** — Fund the home channel with deposit + checkpoint, then create sessions, fund them with submitAppSessionDeposit, submit state, close +6. **Error Handling** — Common errors and how to handle them +7. **Testing** — How to write tests against the SDK + +For each step, show complete TypeScript code examples using the latest SDK API. +Use \`@yellow-org/sdk\` for new projects. Only use \`@yellow-org/sdk-compat\` if migrating from v0.5.3. + +## Go SDK + +Guide me through building a Nitrolite state channel application in Go. Cover: + +1. **Setup** — Install dependencies, create signers with sign.NewEthereumMsgSigner and sign.NewEthereumRawSigner +2. **Client Creation** — sdk.NewClient with functional options (WithBlockchainRPC, WithHandshakeTimeout) +3. **Channel Lifecycle** — Deposit (creates channel), Transfer, Checkpoint (on-chain), CloseHomeChannel + Checkpoint +4. **App Sessions** — Deposit + Checkpoint on the home channel, then CreateAppSession, SubmitAppSessionDeposit, SubmitAppState (Operate/Withdraw/Close intents) +5. **Error Handling** — Go error patterns, context.WithTimeout, defer client.Close() +6. **Testing** — Standard Go test patterns with *_test.go files + +For each step, show complete Go code examples using the latest SDK from github.com/layer-3/nitrolite/sdk/go. +Use github.com/shopspring/decimal for amounts. Use context.Context for all async operations.`, + }, + }], + }), +); + +server.prompt( + 'migrate-from-v053', + 'Interactive migration assistant from @layer-3/nitrolite v0.5.3 to the compat layer', + async () => { + const migrationDocs = readFile(resolve(COMPAT_ROOT, 'docs/migration-overview.md')); + return { + messages: [{ + role: 'user' as const, + content: { + type: 'text' as const, + text: `I need to migrate my app from \`@layer-3/nitrolite\` v0.5.3 to the new SDK. Help me step by step. + +Here is the official migration guide: + +${migrationDocs} + +Walk me through: +1. Installing \`@yellow-org/sdk-compat\` and peer deps +2. Swapping imports (package name change) +3. Replacing create-sign-send-parse pattern with NitroliteClient methods +4. Updating type references if any changed +5. Testing the migrated code + +Ask me to paste my current code so you can provide specific migration instructions.`, + }, + }], + }; + }, +); + +server.prompt( + 'build-ai-agent-app', + 'Guided conversation for building an AI agent that uses Nitrolite for payments', + async () => ({ + messages: [{ + role: 'user' as const, + content: { + type: 'text' as const, + text: `I want to build an AI agent that uses Nitrolite state channels for payments. Guide me through: + +1. **Agent Wallet Setup** — Create a wallet for the agent, configure the SDK client +2. **Channel Management** — Open a channel, deposit funds for the agent to use +3. **Automated Payments** — Implement a payment function the agent can call autonomously +4. **Session Key Delegation** — Set up a session key with spending caps for security +5. **Agent-to-Agent Payments** — Transfer funds between two autonomous agents +6. **Integration** — Wrap SDK methods as tools for an agent framework (LangChain, CrewAI, etc.) +7. **Error Handling** — Handle reconnection, insufficient funds, expired sessions + +For each step, show complete TypeScript code examples using the latest SDK API (\`@yellow-org/sdk\`). +Use \`viem\` for Ethereum interactions. Include proper error handling and logging. + +## Go SDK + +I want to build an AI agent in Go that uses Nitrolite state channels for payments. Guide me through: + +1. **Agent Wallet Setup** — Create signers from a private key, configure the SDK client +2. **Channel Management** — Open a channel, deposit funds for the agent +3. **Automated Payments** — A goroutine-safe payment function using context and mutexes +4. **Session Key Delegation** — Set up session keys with spending caps +5. **Agent-to-Agent Payments** — Transfer between two autonomous Go agents +6. **Graceful Shutdown** — Handle OS signals, defer client.Close(), WaitCh() +7. **Error Handling** — Wrapped errors, retry patterns, connection recovery + +For each step, show complete Go code using github.com/layer-3/nitrolite/sdk/go. +Use context.Context for timeouts. Use decimal.Decimal for amounts. Follow standard Go patterns.`, + }, + }], + }), +); + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Yellow SDK MCP server running on stdio'); +} + +main().catch((err) => { + console.error('Fatal:', err); + process.exit(1); +}); diff --git a/sdk/mcp/tsconfig.json b/sdk/mcp/tsconfig.json new file mode 100644 index 000000000..9ee88030f --- /dev/null +++ b/sdk/mcp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true + }, + "include": ["src"] +} diff --git a/sdk/ts-compat/CLAUDE.md b/sdk/ts-compat/CLAUDE.md new file mode 100644 index 000000000..c739ef26c --- /dev/null +++ b/sdk/ts-compat/CLAUDE.md @@ -0,0 +1,60 @@ +# TypeScript SDK Compat (`@yellow-org/sdk-compat`) + +Curated migration layer that bridges the old `@layer-3/nitrolite` v0.5.3 API to the `@yellow-org/sdk` v1 runtime. Helps existing dApps migrate supported app-facing paths. + +## Quick Reference + +| Command | What it does | +|---------|-------------| +| `npm test` | Run unit tests (Jest) | +| `npm run build` | Compile with tsc | +| `npm run typecheck` | Type check only | + +## Package Details + +- **Name:** `@yellow-org/sdk-compat` +- **Version:** 1.2.0 +- **Peer deps:** `@yellow-org/sdk >=1.2.0`, `viem ^2.0.0` +- **Dev dep:** `"@yellow-org/sdk": "file:../ts"` — **must build `sdk/ts` first** + +## Critical Constraint: No Barrel Re-Export of SDK Classes + +The main SDK (`@yellow-org/sdk`) has side effects on module evaluation that break SSR. + +**DO NOT** add `export { Client } from '@yellow-org/sdk'` to `index.ts`. + +**SAFE:** `export type { StateSigner } from '@yellow-org/sdk'` (type-only exports are erased at compile time). + +This is documented in `src/index.ts`. + +## Source Layout + +| Path | Purpose | +|------|---------| +| `src/index.ts` | Barrel export — update when adding public API | +| `src/client.ts` | NitroliteClient facade (~1100 lines) | +| `src/auth.ts` | Auth helpers (createAuthRequestMessage, etc.) | +| `src/rpc.ts` | RPC compat stubs (create*Message / parse*Response) | +| `src/types.ts` | All types, enums, interfaces | +| `src/signers.ts` | WalletStateSigner, createECDSAMessageSigner | +| `src/app-signing.ts` | App session hash packing | +| `src/errors.ts` | CompatError class hierarchy | +| `src/events.ts` | EventPoller (polling bridge for v0.5.3 push events) | +| `src/config.ts` | Configuration builders | +| `docs/` | Migration guides (overview, on-chain, off-chain) | +| `test/unit/` | Unit tests | + +## Test Setup + +- Framework: Jest with ts-jest +- Config: `jest.config.cjs` +- ESM handling: `transformIgnorePatterns` whitelists `@yellow-org` for ts-jest +- Pattern: manual mock signers (`async () => '0xsig'`) + +## When Adding Exports + +1. Add the function/type to the appropriate source file +2. Export it from `src/index.ts` +3. If re-exporting from `@yellow-org/sdk`, use `export type` only (SSR safety) +4. Update `README.md` (Types Reference, RPC Stubs, or Auth Helpers section) +5. Add or update a focused unit test under `test/unit/` that matches the surface you changed diff --git a/sdk/ts-compat/README.md b/sdk/ts-compat/README.md index d84ce0d1c..764083fe2 100644 --- a/sdk/ts-compat/README.md +++ b/sdk/ts-compat/README.md @@ -2,16 +2,27 @@ [![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. +`@yellow-org/sdk-compat` is a migration layer that preserves selected Nitrolite SDK **v0.5.3 app-facing APIs** over the **v1 runtime**. -- Keep v0.5.3-style app-facing calls in your code. +> The off-chain broker was renamed from "Clearnode" to "Nitronode" in v1.3.0. See [`MIGRATION-NITRONODE.md`](../../MIGRATION-NITRONODE.md). + +- Keep supported v0.5.3-style app-facing calls in your code. - Run them through `@yellow-org/sdk-compat`, backed by `@yellow-org/sdk`. +- Treat it as a migration aid, not a drop-in replacement for the full published v0.5.3 package. + +## Compatibility Scope + +`@yellow-org/sdk-compat` is intentionally narrower than the published v0.5.3 package surface. + +- **Preserved app-facing APIs**: the `NitroliteClient` facade, selected auth helpers, app-session signing helpers, and many app-facing types remain available for supported migration paths. +- **Transitional helper surfaces**: some legacy `create*Message` / `parse*Response` exports now emit real v1-compatible payloads inside the legacy `req`/`sig` envelope, while workflow-only helpers stay exported as fail-fast migration shims. +- **Unsupported full-package parity**: low-level internals, broad root-export parity, and every legacy helper being runtime-faithful are not promised by this package. ## Why -The v1.0.0 protocol introduces breaking changes across 14 dimensions — wire format, authentication, WebSocket lifecycle, unit system, asset resolution, and more. A direct migration touches 20+ files per app with deep, scattered rewrites. +The v1 protocol changes wire format, authentication, WebSocket lifecycle, unit handling, asset resolution, and more. For apps built around the old surface, a direct migration can require scattered rewrites across transport, signing, and amount-handling paths. -The compat layer centralises this complexity into **~1,000 lines** that absorb the protocol differences, reducing per-app integration effort by an estimated **56–70%**. +The compat layer centralizes the supported migration paths into one package so app code can move to client-level methods incrementally instead of rewriting every call site at once. ## Build Size @@ -32,7 +43,7 @@ npm pack --dry-run --json ## Migration Guide -Step-by-step guides for migrating from v0.5.3: +Step-by-step guides for migrating supported app-facing paths from v0.5.3: - [Overview & Quick Start](./docs/migration-overview.md) — pattern changes, import swaps - [On-Chain Changes](./docs/migration-onchain.md) — deposits, withdrawals, channels @@ -56,7 +67,7 @@ Replace `new Client(ws, signer)` with `NitroliteClient.create()`: import { NitroliteClient, blockchainRPCsFromEnv } from '@yellow-org/sdk-compat'; const client = await NitroliteClient.create({ - wsURL: 'wss://clearnode.example.com/ws', + wsURL: 'wss://nitronode.example.com/ws', walletClient, // viem WalletClient with account chainId: 11155111, // Sepolia blockchainRPCs: blockchainRPCsFromEnv(), @@ -65,7 +76,7 @@ const client = await NitroliteClient.create({ ### 2. Deposit & create a channel -In v1.0.0, channel creation is implicit on deposit — no separate `createChannel()` call needed: +In v1, channel creation is implicit on deposit — no separate `createChannel()` call needed: ```typescript const tokenAddress = '0x6E2C4707DA119425DF2C722E2695300154652F56'; // USDC on Sepolia @@ -86,10 +97,10 @@ 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)`: +The compat `transfer(destination, allocations)` preserves the v0.5.3-style array-of-allocations signature. Each `TransferAllocation.amount` is a **raw asset-unit string** using the asset's canonical decimals. The compat layer divides by asset decimals before delegating to the v1 SDK's `transfer(wallet, asset, Decimal)`: ```typescript -// 5 USDC = 5_000_000 raw units (6 decimals) +// 5 USDC = 5_000_000 raw asset units when USDC has 6 asset decimals await client.transfer(recipientAddress, [ { asset: 'usdc', amount: '5000000' }, ]); @@ -136,6 +147,7 @@ await client.close(); | `getEscrowChannel(escrowChannelId)` | Query an escrow channel by ID | | `getChannelData(channelId)` | Full channel + state for a specific channel | | `getLastAppSessionsListError()` | Last `getAppSessionsList()` error message (if any) | +| `getOpenChannels()` | Read current-chain open channel IDs from the ChannelHub | ### App Sessions @@ -146,6 +158,8 @@ await client.close(); | `submitAppState(params)` | Submit state update (operate/deposit/withdraw/close) | | `getAppDefinition(appSessionId)` | Get the definition for a session | +App-session allocation strings remain **human-readable decimal strings** such as `'0.01'`. They are not raw smallest-unit token strings. + ### App Registry | Method | Description | @@ -164,14 +178,21 @@ await client.close(); ### Session Key Operations +Both `state.user_sig` (wallet authorization) and `state.session_key_sig` (proof of +possession by the session-key holder) are required at submit time. The `*Ownership` +helpers produce `session_key_sig`. + | Method | Description | |---|---| -| `signChannelSessionKeyState(state)` | Sign a channel session-key state payload | -| `submitChannelSessionKeyState(state)` | Register/submit a channel session-key state | -| `getLastChannelKeyStates(userAddress, sessionKey?)` | Fetch channel session-key states for wallet/key | -| `signSessionKeyState(state)` | Sign an app-session key state payload | -| `submitSessionKeyState(state)` | Register/submit an app-session key state | -| `getLastKeyStates(userAddress, sessionKey?)` | Fetch app-session key states for wallet/key | +| `signChannelSessionKeyState(state)` | Wallet `user_sig` over channel session-key state | +| `signChannelSessionKeyOwnership(state, sessionKeySigner)` | Session-key holder's `session_key_sig` for channel state | +| `submitChannelSessionKeyState(state)` | Register/submit a channel session-key state (both sigs required) | +| `getLastChannelKeyStates(userAddress, sessionKey?, options?)` | Fetch channel session-key states (active-only by default; `{ includeInactive: true }` for expired/revoked) | +| `signSessionKeyState(state)` | Wallet `user_sig` over app session-key state | +| `signAppSessionKeyOwnership(state, sessionKeySigner)` | Session-key holder's `session_key_sig` for app state | +| `submitSessionKeyState(state)` | Register/submit an app-session key state (both sigs required) | +| `getLastAppKeyStates(userAddress, sessionKey?, options?)` | Fetch app-session key states (active-only by default; `{ includeInactive: true }` for expired/revoked) | +| `getLastKeyStates(userAddress, sessionKey?)` | **Deprecated** — 0.5.x alias for `getLastAppKeyStates`; no `includeInactive` option | ### Transfers @@ -179,6 +200,8 @@ await client.close(); |---|---| | `transfer(destination, allocations)` | Off-chain transfer to another participant | +`TransferAllocation.amount` remains a **raw asset-unit string** using the asset's canonical decimals, for example `'5000000'` for 5 USDC when USDC has 6 asset decimals. + ### Asset Resolution | Method | Description | @@ -191,6 +214,25 @@ await client.close(); | `parseAmount(tokenAddress, humanAmount)` | Convert human-readable string → raw bigint | | `findOpenChannel(tokenAddress, chainId?)` | Find an open channel for a given token | +### Legacy On-Chain Helpers + +| Method | Description | +|---|---| +| `approveTokens(tokenAddress, amount)` | Approve the current-chain ChannelHub using raw token units | +| `getTokenAllowance(tokenAddress)` | Read current-chain ChannelHub allowance in raw token units | +| `getTokenBalance(tokenAddress)` | Read current-chain wallet token balance in raw token units | + +### Unsupported Legacy Gaps + +These legacy methods remain intentionally unsupported because the v1 runtime no longer offers an honest one-to-one mapping: + +- `createChannel(...)` +- `checkpointChannel(...)` +- `getAccountBalance(...)` +- `getChannelBalance(...)` + +Workflow-style RPC helpers such as `createTransferMessage(...)`, `createCreateChannelMessage(...)`, `createCloseChannelMessage(...)`, and `createResizeChannelMessage(...)` are in the same category: they stay exported for migration, but now fail fast with migration guidance instead of silently approximating old behavior. + ### Security Token Locking | Method | Description | @@ -209,11 +251,11 @@ await client.close(); | `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 | +| `refreshAssets()` | Re-fetch the asset map from the nitronode | -### Accessing the v1.0.0 SDK Directly +### Accessing the v1 SDK Directly -The underlying v1.0.0 `Client` is exposed for advanced use cases not covered by the compat surface: +The underlying v1 `Client` is exposed for advanced use cases not covered by the compat surface: ```typescript const v1Client = client.innerClient; @@ -226,7 +268,7 @@ await v1Client.getHomeChannel(wallet, 'usdc'); ```typescript interface NitroliteClientConfig { - wsURL: string; // Clearnode WebSocket URL + wsURL: string; // Nitronode WebSocket URL walletClient: WalletClient; // viem WalletClient with account chainId: number; // Chain ID (e.g. 11155111 for Sepolia) blockchainRPCs?: Record; // Optional chain ID → RPC URL map @@ -253,7 +295,7 @@ NEXT_PUBLIC_BLOCKCHAIN_RPCS=11155111:https://rpc.sepolia.io,1:https://mainnet.in ### `WalletStateSigner` -A v0.5.3-compatible signer class that wraps a `WalletClient`. Actual state signing in v1.0.0 is handled internally by `ChannelDefaultSigner`; this class exists so existing store types compile: +A v0.5.3-compatible signer class that wraps a `WalletClient`. Actual state signing in v1 is handled internally by `ChannelDefaultSigner`; this class exists so existing store types compile: ```typescript import { WalletStateSigner } from '@yellow-org/sdk-compat'; @@ -317,7 +359,7 @@ try { ## Event Polling -v0.5.3 used server-push WebSocket events. v1.0.0 uses a polling model. The `EventPoller` bridges this gap: +v0.5.3 used server-push WebSocket events. v1 uses a polling model. The `EventPoller` bridges this gap: ```typescript import { EventPoller } from '@yellow-org/sdk-compat'; @@ -366,32 +408,40 @@ 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. +The compat layer keeps the old amount conventions explicit instead of flattening them: | 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'` | +| `transfer` | Raw asset-unit string via `TransferAllocation.amount` | `'100000000000000000000'` | +| `createAppSession`, `closeAppSession`, `submitAppState` allocations | Human-readable decimal string | `'100.0'` | > 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. -`create*` helpers are mostly placeholders; `parse*` helpers perform lightweight normalization of known response shapes. -Prefer calling `NitroliteClient` methods directly for new integrations: +The following functions remain exported primarily so legacy `create*Message` / `parse*Response` imports can keep compiling while an app migrates. +Some `create*` helpers emit real live-v1 method names and payload shapes inside the legacy `req` / `sig` envelope. Workflow-only helpers fail fast with migration guidance instead of returning fake wire payloads. `parse*` helpers only do lightweight normalization of known response shapes. +Prefer `NitroliteClient` methods directly for new integrations: ```typescript -// These compile but do nothing meaningful: +// Direct v1-compatible helper mappings: createGetChannelsMessage, parseGetChannelsResponse, createGetLedgerBalancesMessage, parseGetLedgerBalancesResponse, -parseGetLedgerEntriesResponse, parseGetAppSessionsResponse, -createTransferMessage, createAppSessionMessage, parseCreateAppSessionResponse, +parseGetLedgerEntriesResponse, createGetAppSessionsMessage, parseGetAppSessionsResponse, +createGetAppDefinitionMessage, parseGetAppDefinitionResponse, +createAppSessionMessage, parseCreateAppSessionResponse, createCloseAppSessionMessage, parseCloseAppSessionResponse, +createSubmitAppStateMessage, parseSubmitAppStateResponse, +createPingMessage, + +// Migration-only shims that fail fast: +createTransferMessage, createCreateChannelMessage, parseCreateChannelResponse, createCloseChannelMessage, parseCloseChannelResponse, createResizeChannelMessage, parseResizeChannelResponse, -createPingMessage, + +// Generic normalizers: convertRPCToClientChannel, convertRPCToClientState, parseAnyRPCResponse, NitroliteRPC ``` @@ -414,7 +464,7 @@ All legacy compat types are re-exported from `@yellow-org/sdk-compat`: ### Enums - `RPCMethod` — RPC method names (`Ping`, `GetConfig`, `GetChannels`, etc.) -- `RPCChannelStatus` — Channel status values (`Open`, `Closed`, `Resizing`, `Challenged`) +- `RPCChannelStatus` — Channel status values (`Open`, `Closed`, `Resizing`, `Challenged`, `Closing`) ### Wire Types @@ -449,7 +499,7 @@ All legacy compat types are re-exported from `@yellow-org/sdk-compat`: - `State` — Channel state (channelId, version, data, allocations) - `AppLogic` — Interface for custom app logic implementations -### Clearnode Response Types +### Nitronode Response Types - `AccountInfo` — `{ balances: LedgerBalance[], channelCount: bigint }` - `LedgerChannel` — Full ledger channel record (id, participant, status, token, amount, chain_id, etc.) @@ -462,13 +512,13 @@ All legacy compat types are re-exported from `@yellow-org/sdk-compat`: ### `buildClientOptions` -Converts a `CompatClientConfig` into v1.0.0 `Option[]` values passed to `Client.create()`. Useful if you need to customise the underlying SDK client beyond what `NitroliteClient.create()` exposes: +Converts a `CompatClientConfig` into v1 `Option[]` values passed to `Client.create()`. Useful if you need to customise the underlying SDK client beyond what `NitroliteClient.create()` exposes: ```typescript import { buildClientOptions, type CompatClientConfig } from '@yellow-org/sdk-compat'; const opts = buildClientOptions({ - wsURL: 'wss://clearnode.example.com/ws', + wsURL: 'wss://nitronode.example.com/ws', blockchainRPCs: { 11155111: 'https://rpc.sepolia.io' }, }); ``` @@ -491,7 +541,7 @@ const nextConfig = { | Package | Version | |---|---| -| `@yellow-org/sdk` | `>=1.0.0` | +| `@yellow-org/sdk` | `>=1.2.0` | | `viem` | `^2.0.0` | ## License diff --git a/sdk/ts-compat/docs/migration-offchain.md b/sdk/ts-compat/docs/migration-offchain.md index 8b0a77d44..d476cfee9 100644 --- a/sdk/ts-compat/docs/migration-offchain.md +++ b/sdk/ts-compat/docs/migration-offchain.md @@ -1,10 +1,10 @@ # Off-Chain Migration Guide -This guide covers off-chain operations when migrating from v0.5.3 to the compat layer: authentication, app sessions, transfers, ledger queries, event polling, and RPC compatibility helpers. +This guide covers off-chain operations when migrating from v0.5.3 to the compat layer: authentication, app sessions, transfers, ledger queries, event polling, and transitional RPC helper imports. ## 1. Authentication -v1.0.0 handles authentication internally when using `NitroliteClient`. For legacy WebSocket-auth code paths, the compat layer keeps `createAuthRequestMessage`, `createAuthVerifyMessage`, `createAuthVerifyMessageWithJWT`, and `createEIP712AuthMessageSigner` available. +v1 handles authentication internally when using `NitroliteClient`. For legacy WebSocket-auth code paths, the compat layer keeps `createAuthRequestMessage`, `createAuthVerifyMessage`, `createAuthVerifyMessageWithJWT`, and `createEIP712AuthMessageSigner` available. ## 2. App Sessions @@ -23,16 +23,22 @@ parseCreateAppSessionResponse(raw); ``` **After:** `client.createAppSession(definition, allocations)` +`createAppSessionMessage` now emits a real `app_sessions.v1.create_app_session` payload inside the legacy `req` / `sig` envelope, but new integrations should still prefer `client.createAppSession(...)`. + ### Close **Before:** `createCloseAppSessionMessage` + send + parse **After:** `client.closeAppSession(appSessionId, allocations)` +`createCloseAppSessionMessage` maps to `app_sessions.v1.submit_app_state` with `intent = close`, so it now requires an explicit `version` if you keep the helper path. + ### Submit State **Before:** `createSubmitAppStateMessage` + send **After:** `client.submitAppState(params)` +`createSubmitAppStateMessage` also requires `params.version` for the live v1 mapping. + ### Get Definition **Before:** `createGetAppDefinitionMessage` + send + parse @@ -47,14 +53,18 @@ await sendRequest(msg); ``` **After:** `client.transfer(destination, allocations)` +`createTransferMessage` remains exported only so old imports keep compiling, but it now fails fast with migration guidance because transfer is no longer a single direct v1 RPC helper. This is a deliberate runtime change from the old silent placeholder behavior. + ## 4. Ledger Queries **Before:** `createGetLedgerBalancesMessage` / `createGetLedgerEntriesMessage` + send + parse **After:** `client.getBalances()`, `client.getLedgerEntries()` +`createGetLedgerBalancesMessage` now emits a real `user.v1.get_balances` request and requires the wallet/account parameter. `createGetAppSessionsMessage` and `createGetAppDefinitionMessage` likewise emit live v1 request shapes inside the legacy envelope. + ## 5. Event Polling -v0.5.3 used WebSocket push events (`ChannelUpdate`, `BalanceUpdate`). v1.0.0 uses polling. The compat layer provides `EventPoller`: +v0.5.3 used WebSocket push events (`ChannelUpdate`, `BalanceUpdate`). v1 uses polling. The compat layer provides `EventPoller`: ```typescript import { EventPoller } from '@yellow-org/sdk-compat'; @@ -70,4 +80,15 @@ poller.start(); ## 6. RPC Compatibility Helpers -The `create*Message` and `parse*Response` functions still exist so existing imports compile. Most `create*Message` helpers are transitional placeholders; prefer `NitroliteClient` methods directly for new code. Examples: `createGetChannelsMessage`, `parseGetChannelsResponse`, `createTransferMessage`, `createAppSessionMessage`, `createCloseAppSessionMessage`, etc. +The `create*Message` and `parse*Response` exports still exist primarily so legacy imports can keep compiling while you migrate call sites. + +- Direct query/app-session helpers such as `createGetChannelsMessage`, `createGetLedgerBalancesMessage`, `createGetAppSessionsMessage`, `createGetAppDefinitionMessage`, `createAppSessionMessage`, `createSubmitAppStateMessage`, `createCloseAppSessionMessage`, and `createPingMessage` now emit live v1 method names and payload shapes inside the legacy envelope. +- Workflow helpers such as `createTransferMessage` stay exported but fail fast with migration guidance instead of returning fake wire payloads. +- `parse*Response` helpers only normalize known response fields; they do not recreate old payloads that the live v1 server no longer returns. + +For new code, prefer `NitroliteClient` methods directly. + +### Amount conventions + +- `TransferAllocation.amount` remains a raw asset-unit string such as `'5000000'` for 5 USDC when USDC has 6 asset decimals. +- App-session allocation amounts in `createAppSession`, `closeAppSession`, and `submitAppState` remain human-readable decimal strings such as `'0.01'`. diff --git a/sdk/ts-compat/docs/migration-onchain.md b/sdk/ts-compat/docs/migration-onchain.md index 168dc40cb..3b5facfc1 100644 --- a/sdk/ts-compat/docs/migration-onchain.md +++ b/sdk/ts-compat/docs/migration-onchain.md @@ -8,7 +8,6 @@ This guide covers on-chain operations when migrating from v0.5.3 to the compat l ```typescript await approveToken(custody, tokenAddress, amount); -await sendRequest(createDepositMessage(signer.sign, { token: tokenAddress, amount })); await sendRequest(createCreateChannelMessage(signer.sign, { token: tokenAddress, amount })); ``` @@ -18,6 +17,10 @@ await sendRequest(createCreateChannelMessage(signer.sign, { token: tokenAddress, await client.deposit(tokenAddress, amount); ``` +The legacy `createChannel()` client method and `createCreateChannelMessage(...)` helper still exist for migration, but they now throw with migration guidance instead of warning or fabricating a fake wire payload. Use `deposit(...)` or `depositAndCreateChannel(...)` instead. + +`createCreateChannelMessage` remains exported so old imports keep compiling, but it now fails fast with migration guidance because channel creation is no longer a standalone protocol-backed RPC in v1. + ## 2. Withdrawals **Before (v0.5.3):** Manual close → checkpoint → withdraw @@ -26,7 +29,6 @@ await client.deposit(tokenAddress, amount); const closeMsg = await createCloseChannelMessage(signer.sign, { channel_id }); const raw = await sendRequest(closeMsg); // ... checkpoint on-chain ... -await sendRequest(createWithdrawMessage(signer.sign, { token, amount })); ``` **After (compat):** Single call @@ -35,22 +37,21 @@ await sendRequest(createWithdrawMessage(signer.sign, { token, amount })); await client.withdrawal(tokenAddress, amount); ``` +`createCloseChannelMessage` now fails fast with migration guidance instead of pretending to be a real v1 wire helper. + ## 3. Channel Operations +Legacy channel helper imports may still exist to keep migration moving, but the supported path is the compat client methods below. Do not treat every legacy helper name as a protocol-backed one-to-one v1 RPC. + | Operation | v0.5.3 | Compat | |-----------|--------|--------| -| Create | Explicit `createChannel()` | Implicit on first `deposit()` | -| Close | `createCloseChannelMessage` + send + parse | `client.closeChannel()` | -| Resize | `createResizeChannelMessage` + send + parse | `client.resizeChannel({ allocate_amount, token })` | +| Create | Explicit `createChannel()` (now throwing migration shim) | Implicit on first `deposit()` | +| Close | `createCloseChannelMessage` (now fail-fast shim) | `client.closeChannel()` | +| Resize | `createResizeChannelMessage` (now fail-fast shim) | `client.resizeChannel({ allocate_amount, token })` | **Example — close:** ```typescript -// Before -const msg = await createCloseChannelMessage(signer.sign, { channel_id }); -const raw = await sendRequest(msg); -const parsed = parseCloseChannelResponse(raw); - // After await client.closeChannel(); ``` @@ -64,7 +65,7 @@ const amount = 11_000_000n; // 11 USDC (6 decimals) // Manual conversion for display: formatUnits(amount, 6) ``` -**After (compat):** Accepts both; conversion handled internally +**After (compat):** On-chain app-facing methods still accept raw token amounts, and the compat layer handles the conversion needed to call the v1 SDK correctly ```typescript // Raw BigInt still works @@ -75,7 +76,9 @@ const formatted = client.formatAmount(tokenAddress, 11_000_000n); // "11.0" const parsed = client.parseAmount(tokenAddress, "11.0"); // 11_000_000n ``` -For transfers and allocations, compat accepts human-readable strings: `{ asset: 'usdc', amount: '5.0' }`. +Transfer allocations still use raw asset-unit strings, for example `{ asset: 'usdc', amount: '5000000' }` for 5 USDC when USDC has 6 asset decimals. + +App-session allocations are different: those remain human-readable decimal strings such as `{ asset: 'usdc', amount: '5.0' }`. ## 5. Contract Addresses @@ -88,4 +91,4 @@ const addresses = { }; ``` -**After (compat):** Fetched from clearnode `get_config` — no manual setup. The `addresses` field in config is deprecated and ignored. +**After (compat):** Fetched from nitronode `get_config` — no manual setup. The `addresses` field in config is deprecated and ignored. diff --git a/sdk/ts-compat/docs/migration-overview.md b/sdk/ts-compat/docs/migration-overview.md index a90453983..930688aaf 100644 --- a/sdk/ts-compat/docs/migration-overview.md +++ b/sdk/ts-compat/docs/migration-overview.md @@ -1,12 +1,10 @@ # Migrating from v0.5.3 to Compat Layer -This guide explains how to migrate your Nitrolite dApp from the v0.5.3 SDK to the **compat layer**, which bridges the old API to the v1.0.0 runtime with minimal code changes. +This guide explains how to migrate your Nitrolite dApp from the v0.5.3 SDK to the **compat layer**, a curated migration layer that preserves selected app-facing APIs over the v1 runtime. ## 1. Why Use the Compat Layer -The v1.0.0 protocol introduces breaking changes across wire format, authentication, WebSocket lifecycle, unit system, asset resolution, and more. A direct migration touches **20+ files** per app with deep, scattered rewrites. - -The compat layer centralises this complexity into **~5 file changes** per app. Instead of rewriting every RPC call, you swap imports and replace the `create-sign-send-parse` pattern with direct client method calls. +The v1 protocol changes wire format, authentication, WebSocket lifecycle, unit handling, and asset resolution. Instead of rewriting every RPC call at once, the compat layer lets supported app-facing paths move over incrementally by swapping imports and replacing the `create-sign-send-parse` pattern with direct client method calls. ## 2. Installation @@ -21,9 +19,9 @@ npm install @yellow-org/sdk viem | Before (v0.5.3) | After (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` | +| Types: `AppSession`, `LedgerChannel`, `RPCAppDefinition` | Many app-facing types remain 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`. +For **types**, many app-facing imports only need a package-name swap. For **functions**, prefer client methods instead of `create*Message` / `parse*Response`. Some legacy helper imports remain exported as direct v1-compatible request builders; workflow-style helpers stay exported as fail-fast migration shims so imports keep compiling while call sites move. ## 4. The Key Pattern Change @@ -45,8 +43,8 @@ const channels = await client.getChannels(); ## 5. What Stays the Same -- **Type shapes:** `AppSession`, `LedgerChannel`, `RPCAppDefinition`, `RPCBalance`, `RPCAsset`, etc. -- **Response formats:** Balances, ledger entries, app sessions — same structure as v0.5.3. +- **Many app-facing type shapes:** `AppSession`, `LedgerChannel`, `RPCAppDefinition`, `RPCBalance`, `RPCAsset`, etc. +- **Familiar response shapes:** balances, ledger entries, and app sessions remain close to v0.5.3 for the supported app-facing paths. - **Auth helpers:** `createAuthRequestMessage`, `createAuthVerifyMessage`, `createAuthVerifyMessageWithJWT`, and `createEIP712AuthMessageSigner` remain available for legacy auth flows. ## 6. What Changes @@ -55,8 +53,8 @@ const channels = await client.getChannels(); |---------|--------|--------| | WebSocket | App creates and manages `WebSocket` | Managed internally by the client | | Signing | App passes `signer.sign` into every message | Internal — client uses `WalletClient` | -| Amounts | Raw `BigInt` everywhere | Compat accepts both; conversion handled internally | -| Contract addresses | Manual config | Fetched from clearnode `get_config` | +| Amounts | Raw `BigInt` everywhere | Compat keeps legacy amount conventions explicit: transfers stay raw asset-unit strings, app-session allocations stay human-decimal strings | +| Contract addresses | Manual config | Fetched from nitronode `get_config` | | Channel creation | Explicit `createChannel()` | Implicit on first `deposit()` | ## 7. Quick Start Example @@ -66,7 +64,7 @@ import { NitroliteClient, WalletStateSigner, blockchainRPCsFromEnv } from '@yell // Create client (replaces new Client(ws, signer)) const client = await NitroliteClient.create({ - wsURL: 'wss://clearnode.example.com/ws', + wsURL: 'wss://nitronode.example.com/ws', walletClient, // viem WalletClient with account chainId: 11155111, // Sepolia blockchainRPCs: blockchainRPCsFromEnv(), @@ -81,7 +79,7 @@ const balances = await client.getBalances(); const sessions = await client.getAppSessionsList(); // Transfer -await client.transfer(recipientAddress, [{ asset: 'usdc', amount: '5.0' }]); +await client.transfer(recipientAddress, [{ asset: 'usdc', amount: '5000000' }]); // Cleanup await client.closeChannel(); diff --git a/sdk/ts-compat/eslint.config.cjs b/sdk/ts-compat/eslint.config.cjs new file mode 100644 index 000000000..4c7040929 --- /dev/null +++ b/sdk/ts-compat/eslint.config.cjs @@ -0,0 +1,26 @@ +const tseslint = require('@typescript-eslint/eslint-plugin'); +const tsParser = require('@typescript-eslint/parser'); + +module.exports = [ + { + ignores: ['dist/**', 'node_modules/**'], + }, + { + files: ['src/**/*.ts', 'test/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + ...tseslint.configs.recommended.rules, + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + }, + }, +]; diff --git a/sdk/ts-compat/examples/lifecycle.ts b/sdk/ts-compat/examples/lifecycle.ts index 4731296a0..362ce906f 100644 --- a/sdk/ts-compat/examples/lifecycle.ts +++ b/sdk/ts-compat/examples/lifecycle.ts @@ -12,7 +12,7 @@ * * Prerequisites: * - PRIVATE_KEY env var set to an Ethereum private key - * - CLEARNODE_WS_URL env var (defaults to wss://clearnode-sandbox.yellow.org/v1/ws) + * - NITRONODE_WS_URL env var (defaults to wss://nitronode-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-sandbox.yellow.org/v1/ws'; +const WS_URL = process.env.NITRONODE_WS_URL || 'wss://nitronode-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'); @@ -128,7 +128,7 @@ async function main() { const account = privateKeyToAccount(PRIVATE_KEY); console.log(`Wallet: ${account.address}`); - console.log(`Clearnode: ${WS_URL}`); + console.log(`Nitronode: ${WS_URL}`); console.log(`Chain: ${CHAIN_ID}\n`); const walletClient = createWalletClient({ diff --git a/sdk/ts-compat/examples/output.txt b/sdk/ts-compat/examples/output.txt index d7563d30b..3c52e2d54 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-sandbox.yellow.org/v1/ws +Nitronode: wss://nitronode-sandbox.yellow.org/v1/ws Chain: 11155111 -- 1. Client Initialization -- diff --git a/sdk/ts-compat/jest.config.cjs b/sdk/ts-compat/jest.config.cjs new file mode 100644 index 000000000..03d7d1009 --- /dev/null +++ b/sdk/ts-compat/jest.config.cjs @@ -0,0 +1,25 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + roots: ['/test/unit'], + testMatch: ['**/*.test.ts'], + extensionsToTreatAsEsm: ['.ts'], + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + useESM: true, + tsconfig: { + module: 'Node16', + moduleResolution: 'Node16', + types: ['node', 'jest'], + rootDir: '.', + }, + }, + ], + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, +}; diff --git a/sdk/ts-compat/package-lock.json b/sdk/ts-compat/package-lock.json index 3fa5e897b..9a7110504 100644 --- a/sdk/ts-compat/package-lock.json +++ b/sdk/ts-compat/package-lock.json @@ -1,19 +1,25 @@ { "name": "@yellow-org/sdk-compat", - "version": "1.1.1", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@yellow-org/sdk-compat", - "version": "1.1.1", + "version": "1.3.0", "license": "MIT", "dependencies": { "decimal.js": "^10.4.3" }, "devDependencies": { + "@types/jest": "30.0.0", "@types/node": "^25.3.0", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", "@yellow-org/sdk": "file:../ts", + "eslint": "^10.0.0", + "jest": "^30.2.0", + "ts-jest": "^29.1.2", "tsx": "^4.21.0", "typescript": "^5.9.3", "viem": "^2.46.2" @@ -22,20 +28,20 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@yellow-org/sdk": ">=1.0.0", + "@yellow-org/sdk": ">=1.2.1", "viem": "^2.0.0" } }, "../ts": { "name": "@yellow-org/sdk", - "version": "1.1.0", + "version": "1.3.0", "dev": true, "license": "MIT", "dependencies": { "abitype": "^1.2.3", "decimal.js": "^10.4.3", - "jest-util": "^30.2.0", - "viem": "^2.46.1", + "jest-util": "^30.3.0", + "viem": "^2.50.4", "zod": "^4.3.6" }, "devDependencies": { @@ -53,11 +59,12 @@ "ethers": "6.16.0", "glob": "^13.0.3", "jest": "^30.2.0", - "prettier": "3.8.1", + "prettier": "3.8.3", "rimraf": "^6.1.3", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.3.0" + "typescript": "^6.0.3", + "ws": "8.21.0" }, "engines": { "node": ">=20.0.0" @@ -70,333 +77,594 @@ "dev": true, "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@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": ">=18" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], + "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==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], + "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==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], + "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==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], + "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==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], + "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==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], + "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==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { + "node_modules/@esbuild/android-arm": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { + "node_modules/@esbuild/android-arm64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -404,16 +672,16 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-x64": { + "node_modules/@esbuild/android-x64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -421,16 +689,16 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openharmony-arm64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -438,16 +706,16 @@ "license": "MIT", "optional": true, "os": [ - "openharmony" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { + "node_modules/@esbuild/darwin-x64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -455,16 +723,16 @@ "license": "MIT", "optional": true, "os": [ - "sunos" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { + "node_modules/@esbuild/freebsd-arm64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -472,303 +740,5157 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-ia32": { + "node_modules/@esbuild/freebsd-x64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-x64": { + "node_modules/@esbuild/linux-arm": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { "url": "https://paulmillr.com/funding/" } }, - "node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.3.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", + "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", + "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/type-utils": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", + "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", + "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.0", + "@typescript-eslint/types": "^8.59.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", + "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", + "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", + "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", + "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", + "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.0", + "@typescript-eslint/tsconfig-utils": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", + "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", + "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yellow-org/sdk": { + "resolved": "../ts", + "link": true + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.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==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "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/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "dev": true, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/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==", + "dev": true, + "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/cliui/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "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/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.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==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "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/execa/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ox": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.12.4.tgz", + "integrity": "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "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==", + "dev": true, + "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/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "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" + } + }, + "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==", "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "1.8.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": "^14.21.3 || >=16" + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { - "node": "^14.21.3 || >=16" + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=10" } }, - "node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=8" } }, - "node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=10" } }, - "node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "node_modules/string-length/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/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==", "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/node": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", - "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "node_modules/string-width-cjs": { + "name": "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==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@yellow-org/sdk": { - "resolved": "../ts", - "link": true + "node_modules/string-width-cjs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/abitype": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", - "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "node_modules/string-width-cjs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, "funding": { - "url": "https://github.com/sponsors/wevm" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3.22.0 || ^4.0.0" + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "@pkgr/core": "^0.2.9" }, "engines": { - "node": ">=18" + "node": "^14.18.0 || >=16.0.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "funding": { + "url": "https://opencollective.com/synckit" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/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==", "dev": true, "license": "MIT" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "*" } }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], "license": "MIT", + "engines": { + "node": ">=18.12" + }, "peerDependencies": { - "ws": "*" + "typescript": ">=4.8.4" } }, - "node_modules/ox": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.12.4.tgz", - "integrity": "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q==", + "node_modules/ts-jest": { + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], "license": "MIT", "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.2.3", - "eventemitter3": "5.0.1" + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.4", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { - "typescript": ">=5.4.0" + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { - "typescript": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { "optional": true } } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -789,6 +5911,42 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -803,6 +5961,20 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -810,6 +5982,97 @@ "dev": true, "license": "MIT" }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "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==", + "dev": true, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/viem": { "version": "2.46.2", "resolved": "https://registry.npmjs.org/viem/-/viem-2.46.2.tgz", @@ -841,6 +6104,165 @@ } } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "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-cjs": { + "name": "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==", + "dev": true, + "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/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "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/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -863,16 +6285,108 @@ } } }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/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==", + "dev": true, + "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/yargs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/colinhacks" + "url": "https://github.com/sponsors/sindresorhus" } } } diff --git a/sdk/ts-compat/package.json b/sdk/ts-compat/package.json index b557d5a94..6f5ef2905 100644 --- a/sdk/ts-compat/package.json +++ b/sdk/ts-compat/package.json @@ -1,7 +1,7 @@ { "name": "@yellow-org/sdk-compat", - "version": "1.2.0", - "description": "Compatibility layer bridging Nitrolite SDK v0.5.3 API to v1.0.0, minimising migration effort for existing dApps.", + "version": "1.3.0", + "description": "Curated migration layer preserving selected Nitrolite SDK v0.5.3 app-facing APIs over the v1 runtime.", "type": "module", "sideEffects": false, "main": "dist/index.js", @@ -12,7 +12,11 @@ ], "scripts": { "build": "tsc", + "build:ci": "tsc", "build:prod": "tsc -p tsconfig.prod.json", + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config jest.config.cjs", + "drift:check": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config jest.config.cjs --runTestsByPath test/unit/client.test.ts test/unit/config.test.ts test/unit/public-api-drift.test.ts", + "lint": "eslint src test", "typecheck": "tsc --noEmit", "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"" }, @@ -35,15 +39,21 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@yellow-org/sdk": ">=1.2.0", + "@yellow-org/sdk": ">=1.2.1", "viem": "^2.0.0" }, "dependencies": { "decimal.js": "^10.4.3" }, "devDependencies": { + "@types/jest": "30.0.0", "@yellow-org/sdk": "file:../ts", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", "@types/node": "^25.3.0", + "eslint": "^10.0.0", + "jest": "^30.2.0", + "ts-jest": "^29.1.2", "tsx": "^4.21.0", "typescript": "^5.9.3", "viem": "^2.46.2" diff --git a/sdk/ts-compat/src/app-signing.ts b/sdk/ts-compat/src/app-signing.ts index 18122f872..5031812c5 100644 --- a/sdk/ts-compat/src/app-signing.ts +++ b/sdk/ts-compat/src/app-signing.ts @@ -1,6 +1,6 @@ import { Address, Hex, concatHex, encodeAbiParameters, keccak256 } from 'viem'; -import { RPCAppStateIntent } from './types'; +import { RPCAppStateIntent } from './types.js'; const WALLET_QUORUM_PREFIX = '0xa1' as Hex; const SESSION_KEY_QUORUM_PREFIX = '0xa2' as Hex; diff --git a/sdk/ts-compat/src/auth.ts b/sdk/ts-compat/src/auth.ts index 881554542..239184a3c 100644 --- a/sdk/ts-compat/src/auth.ts +++ b/sdk/ts-compat/src/auth.ts @@ -1,13 +1,13 @@ /** * Auth functions -- real implementations matching v0.5.3 SDK behavior. * - * These create properly formatted RPC messages for the clearnode's + * These create properly formatted RPC messages for the nitronode's * auth_request / auth_verify flow over WebSocket. */ -import { NitroliteRPC } from './rpc'; -import { RPCMethod, EIP712AuthTypes } from './types'; -import type { MessageSigner, MessageSignerPayload } from './types'; +import { NitroliteRPC } from './rpc.js'; +import { RPCMethod, EIP712AuthTypes } from './types.js'; +import type { MessageSigner, MessageSignerPayload } from './types.js'; export interface AuthRequestParams { address: string; diff --git a/sdk/ts-compat/src/client.ts b/sdk/ts-compat/src/client.ts index b6d7134bb..840276d8a 100644 --- a/sdk/ts-compat/src/client.ts +++ b/sdk/ts-compat/src/client.ts @@ -2,6 +2,7 @@ import { Client, ChannelDefaultSigner, ChannelSessionKeyStateSigner, + type EthereumMsgSigner, type StateSigner, type TransactionSigner, } from '@yellow-org/sdk'; @@ -14,8 +15,17 @@ import type { ChannelSessionKeyStateV1, } from '@yellow-org/sdk'; import type * as core from '@yellow-org/sdk'; -import Decimal from 'decimal.js'; -import { Address, Hex, WalletClient, createPublicClient, http, formatUnits, parseUnits } from 'viem'; +import { Decimal } from 'decimal.js'; +import { + Address, + Hex, + PublicClient, + WalletClient, + createPublicClient, + formatUnits, + http, + parseUnits, +} from 'viem'; import type { RPCBalance, @@ -36,17 +46,17 @@ import type { GetAppDefinitionResponseParams, CreateAppSessionRequestParams, CloseAppSessionRequestParams, -} from './types'; -import { RPCAppStateIntent } from './types'; +} from './types.js'; +import { RPCAppStateIntent } from './types.js'; -import { buildClientOptions, type CompatClientConfig } from './config'; +import { buildClientOptions, type CompatClientConfig } from './config.js'; import { AllowanceError, InsufficientFundsError, NotInitializedError, OngoingStateTransitionError, UserRejectedError, -} from './errors'; +} from './errors.js'; // --------------------------------------------------------------------------- // WalletClient-based signers for browser (MetaMask) environments @@ -101,10 +111,21 @@ class WalletTxSigner implements TransactionSigner { interface AssetInfo { symbol: string; chainId: bigint; - decimals: number; + assetDecimals: number; + tokenDecimals: number; tokenAddress: string; } +const ChannelHubCompatAbi = [ + { + type: 'function', + stateMutability: 'view', + name: 'getOpenChannels', + inputs: [{ name: 'user', type: 'address' }], + outputs: [{ name: '', type: 'bytes32[]' }], + }, +] as const; + // --------------------------------------------------------------------------- // NitroliteClient compat facade // --------------------------------------------------------------------------- @@ -130,7 +151,9 @@ export class NitroliteClient { /** The underlying v1.0.0 SDK Client -- use for any functionality not wrapped by compat. */ readonly innerClient: Client; readonly userAddress: Address; + private readonly walletClient: WalletClient; + private assetsByChainAndToken = new Map(); // `${chainId}:${tokenAddr}` -> info private assetsByToken = new Map(); // lowercase tokenAddr -> info private assetsBySymbol = new Map(); // lowercase symbol -> info private _chainId: bigint; @@ -140,10 +163,18 @@ export class NitroliteClient { private _blockchains: core.Blockchain[] | null = null; private _lockingTokenDecimals = new Map(); private _blockchainRPCs: Record; + private _publicClients = new Map(); - private constructor(client: Client, userAddress: Address, chainId: number, blockchainRPCs?: Record) { + private constructor( + client: Client, + userAddress: Address, + chainId: number, + walletClient: WalletClient, + blockchainRPCs?: Record, + ) { this.innerClient = client; this.userAddress = userAddress; + this.walletClient = walletClient; this._chainId = BigInt(chainId); this._blockchainRPCs = blockchainRPCs ?? {}; } @@ -184,7 +215,13 @@ export class NitroliteClient { const v1Client = await Client.create(config.wsURL, stateSigner, txSigner, ...opts); - const compat = new NitroliteClient(v1Client, walletAddress, config.chainId, config.blockchainRPCs); + const compat = new NitroliteClient( + v1Client, + walletAddress, + config.chainId, + config.walletClient, + config.blockchainRPCs, + ); try { await compat.refreshAssets(); @@ -199,8 +236,13 @@ export class NitroliteClient { // Asset mapping // ----------------------------------------------------------------------- + private assetTokenKey(chainId: bigint, tokenAddress: string): string { + return `${chainId}:${tokenAddress.toLowerCase()}`; + } + async refreshAssets(): Promise { const assets = await this.innerClient.getAssets(); + this.assetsByChainAndToken.clear(); this.assetsByToken.clear(); this.assetsBySymbol.clear(); @@ -209,11 +251,13 @@ export class NitroliteClient { const info: AssetInfo = { symbol: asset.symbol, chainId: token.blockchainId, - decimals: asset.decimals, + assetDecimals: asset.decimals, + tokenDecimals: token.decimals, tokenAddress: token.address.toLowerCase(), }; - this.assetsByToken.set(info.tokenAddress, info); + this.assetsByChainAndToken.set(this.assetTokenKey(info.chainId, info.tokenAddress), info); if (token.blockchainId === this._chainId) { + this.assetsByToken.set(info.tokenAddress, info); this.assetsBySymbol.set(asset.symbol.toLowerCase(), info); } } @@ -224,13 +268,49 @@ export class NitroliteClient { if (this.assetsByToken.size === 0) await this.refreshAssets(); } - private async getDecimalsForAsset(assetSymbol: string): Promise { + private async getTokenDecimalsForAsset(assetSymbol: string): Promise { await this.ensureAssets(); const info = this.assetsBySymbol.get(assetSymbol.toLowerCase()); if (!info) { - console.warn(`[compat] Unknown asset symbol ${assetSymbol}, falling back to 6 decimals`); + console.warn(`[compat] Unknown asset symbol ${assetSymbol}, falling back to 6 token decimals`); } - return info?.decimals ?? 6; + return info?.tokenDecimals ?? 6; + } + + private async getAssetDecimalsForAsset(assetSymbol: string): Promise { + await this.ensureAssets(); + const info = this.assetsBySymbol.get(assetSymbol.toLowerCase()); + if (!info) { + console.warn(`[compat] Unknown asset symbol ${assetSymbol}, falling back to 6 asset decimals`); + } + return info?.assetDecimals ?? 6; + } + + private async getTokenInfoForChain(chainId: bigint, tokenAddress: string): Promise { + await this.ensureAssets(); + return this.assetsByChainAndToken.get(this.assetTokenKey(chainId, tokenAddress)) ?? null; + } + + private async getTokenDecimalsForChannel( + assetSymbol: string, + chainId: bigint, + tokenAddress?: string, + ): Promise { + if (tokenAddress) { + const info = await this.getTokenInfoForChain(chainId, tokenAddress.toLowerCase()); + if (info) { + return info.tokenDecimals; + } + } + + if (chainId === this._chainId) { + return this.getTokenDecimalsForAsset(assetSymbol); + } + + const assets = await this.innerClient.getAssets(); + const asset = assets.find((candidate) => candidate.symbol.toLowerCase() === assetSymbol.toLowerCase()); + const token = asset?.tokens.find((candidate) => candidate.blockchainId === chainId); + return token?.decimals ?? asset?.decimals ?? 6; } async resolveToken(tokenAddress: Address | string): Promise { @@ -259,7 +339,7 @@ export class NitroliteClient { if (!info) { console.warn(`[compat] Unknown token ${tokenAddress}, falling back to 6 decimals`); } - return info?.decimals ?? 6; + return info?.tokenDecimals ?? 6; } async formatAmount(tokenAddress: Address | string, rawAmount: bigint): Promise { @@ -275,9 +355,11 @@ export class NitroliteClient { async resolveAssetDisplay(tokenAddress: Address | string, _chainId?: number): Promise<{ symbol: string; decimals: number } | null> { await this.ensureAssets(); const key = tokenAddress.toString().toLowerCase(); - const info = this.assetsByToken.get(key); + const info = _chainId !== undefined + ? await this.getTokenInfoForChain(BigInt(_chainId), key) + : this.assetsByToken.get(key) ?? null; if (!info) return null; - return { symbol: info.symbol, decimals: info.decimals }; + return { symbol: info.symbol, decimals: info.tokenDecimals }; } findOpenChannel(tokenAddress: Address | string, chainId?: number): LedgerChannel | null { @@ -298,11 +380,55 @@ export class NitroliteClient { }; } + private getRPCUrl(chainId: number): string { + const configured = this._blockchainRPCs[chainId]; + if (configured) { + return configured; + } + + const walletChain = this.walletClient.chain; + if (walletChain && Number(walletChain.id) === chainId) { + const walletRpcUrl = walletChain.rpcUrls.public?.http?.[0] + ?? walletChain.rpcUrls.default?.http?.[0]; + if (walletRpcUrl) { + return walletRpcUrl; + } + } + + throw new Error( + `[compat] No RPC URL configured for chain ${chainId}. Pass blockchainRPCs when creating NitroliteClient.`, + ); + } + + private getReadClientForChain(chainId: bigint): PublicClient { + const key = Number(chainId); + const cached = this._publicClients.get(key); + if (cached) { + return cached; + } + + const walletChain = this.walletClient.chain; + const client = createPublicClient({ + ...(walletChain && Number(walletChain.id) === key ? { chain: walletChain } : {}), + transport: http(this.getRPCUrl(key)), + }); + this._publicClients.set(key, client); + return client; + } + + private async getBlockchainById(chainId: bigint): Promise { + const blockchains = await this.ensureBlockchains(); + const blockchain = blockchains.find((candidate) => candidate.id === chainId); + if (!blockchain) { + throw new Error(`Unknown blockchain ${chainId.toString()}`); + } + return blockchain; + } + // ----------------------------------------------------------------------- // On-chain operations (v0.5.3 compat surface) // ----------------------------------------------------------------------- - private static readonly MAX_UINT256 = 2n ** 256n - 1n; private static readonly DEFAULT_APPROVE_AMOUNT = new Decimal(100000); /** Classify raw SDK/wallet errors into typed compat errors. */ @@ -334,9 +460,9 @@ export class NitroliteClient { } async deposit(tokenAddress: Address, amount: bigint): Promise { - const { symbol, chainId, decimals, tokenAddress: resolvedAddr } = await this.resolveToken(tokenAddress); + const { symbol, chainId, tokenDecimals, tokenAddress: resolvedAddr } = await this.resolveToken(tokenAddress); await this.innerClient.setHomeBlockchain(symbol, chainId).catch(() => {}); - const humanAmount = this.toHumanAmount(amount, decimals); + const humanAmount = this.toHumanAmount(amount, tokenDecimals); await this.innerClient.deposit(chainId, symbol, humanAmount); return await this.checkpointWithApproval(symbol, chainId, resolvedAddr); } @@ -345,8 +471,10 @@ export class NitroliteClient { return this.deposit(tokenAddress, amount); } - async createChannel(_respParams?: any): Promise { - console.warn('[compat] createChannel is implicit in v1.0.0 -- use deposit() instead'); + async createChannel(_respParams?: any): Promise { + throw new Error( + '[compat] createChannel is not supported as a standalone v1 flow. Use deposit(tokenAddress, amount) or depositAndCreateChannel(...) instead.', + ); } async closeChannel(params?: { tokenAddress?: Address | string } | any): Promise { @@ -367,8 +495,7 @@ export class NitroliteClient { for (const ch of openChannels) { try { - await this.ensureAssets(); - const info = this.assetsByToken.get(ch.token.toLowerCase()); + const info = await this.getTokenInfoForChain(BigInt(ch.chain_id), ch.token.toLowerCase()); const symbol = info?.symbol; if (!symbol) continue; @@ -389,31 +516,92 @@ export class NitroliteClient { } async withdrawal(tokenAddress: Address, amount: bigint): Promise { - const { symbol, chainId, decimals, tokenAddress: resolvedAddr } = await this.resolveToken(tokenAddress); + const { symbol, chainId, tokenDecimals, tokenAddress: resolvedAddr } = await this.resolveToken(tokenAddress); await this.innerClient.setHomeBlockchain(symbol, chainId).catch(() => {}); - const humanAmount = this.toHumanAmount(amount, decimals); + const humanAmount = this.toHumanAmount(amount, tokenDecimals); await this.innerClient.withdraw(chainId, symbol, humanAmount); return await this.checkpointWithApproval(symbol, chainId, resolvedAddr); } async getChannelData(_channelId: string): Promise { await this.ensureAssets(); + let lookupError: unknown = null; for (const [, info] of this.assetsBySymbol) { + let ch; try { - const ch = await this.innerClient.getHomeChannel(this.userAddress, info.symbol); - if (ch.channelId === _channelId) { - return { - channel: ch, - state: await this.innerClient.getLatestState(this.userAddress, info.symbol, false), - }; - } - } catch { - // no channel for this asset + ch = await this.innerClient.getHomeChannel(this.userAddress, info.symbol); + } catch (err) { + lookupError = err; + continue; + } + if (!ch || ch.channelId !== _channelId) { + continue; + } + const state = await this.innerClient.getLatestState(this.userAddress, info.symbol, false); + if (!state) { + throw new Error(`Channel ${_channelId} has no latest state`); } + return { channel: ch, state }; + } + // Any uninspected asset (caught exception) invalidates a not-found conclusion, + // since genuine absence comes back as null under the nullable contract. + if (lookupError) { + throw lookupError instanceof Error + ? lookupError + : new Error(`failed to query channels: ${String(lookupError)}`); } throw new Error(`Channel ${_channelId} not found`); } + async checkpointChannel(_params: unknown): Promise { + throw new Error( + '[compat] checkpointChannel is not supported as a direct v0.5.3 parity method. Use asset-driven v1 flows such as deposit(), withdrawal(), closeChannel(), or client.innerClient.checkpoint(asset).', + ); + } + + async getOpenChannels(): Promise { + const blockchain = await this.getBlockchainById(this._chainId); + const client = this.getReadClientForChain(this._chainId); + const channelIds = await client.readContract({ + address: blockchain.channelHubAddress, + abi: ChannelHubCompatAbi, + functionName: 'getOpenChannels', + args: [this.userAddress], + }); + + return [...channelIds]; + } + + async getAccountBalance(_tokenAddress: Address | Address[]): Promise { + throw new Error( + '[compat] getAccountBalance is not supported as a direct v0.5.3 parity method. Use getBalances() for nitronode ledger balances or getTokenBalance(tokenAddress) for on-chain wallet balances.', + ); + } + + async getChannelBalance(_channelId: string, _tokenAddress: Address | Address[]): Promise { + throw new Error( + '[compat] getChannelBalance is not supported as a direct v0.5.3 parity method. Use getChannelData(channelId) and inspect the returned state balances instead.', + ); + } + + async approveTokens(tokenAddress: Address, amount: bigint): Promise { + const { symbol, chainId, tokenDecimals } = await this.resolveToken(tokenAddress); + const humanAmount = this.toHumanAmount(amount, tokenDecimals); + return this.innerClient.approveToken(chainId, symbol, humanAmount); + } + + async getTokenAllowance(tokenAddress: Address): Promise { + const info = await this.resolveToken(tokenAddress); + return this.innerClient.checkTokenAllowance(info.chainId, info.tokenAddress, this.userAddress); + } + + async getTokenBalance(tokenAddress: Address): Promise { + const info = await this.resolveToken(tokenAddress); + const balance = await this.innerClient.getOnChainBalance(info.chainId, info.symbol, this.userAddress); + const normalized = balance.toDecimalPlaces(info.tokenDecimals, Decimal.ROUND_DOWN); + return parseUnits(normalized.toString(), info.tokenDecimals); + } + // ----------------------------------------------------------------------- // Off-chain queries (for hooks to call directly) // ----------------------------------------------------------------------- @@ -422,7 +610,8 @@ export class NitroliteClient { 0: 'void', 1: 'open', 2: 'challenged', - 3: 'closed', + 3: 'closing', + 4: 'closed', }; async getChannels(): Promise { @@ -444,11 +633,14 @@ export class NitroliteClient { if (ch.status === 1) { try { const state = await this.innerClient.getLatestState(this.userAddress, ch.asset, false); - const raw = state.homeLedger?.userBalance; + const raw = state?.homeLedger?.userBalance; if (raw) { - const info = this.assetsBySymbol.get(ch.asset.toLowerCase()); - const dec = info?.decimals ?? 6; + const dec = await this.getTokenDecimalsForChannel( + ch.asset, + ch.blockchainId ?? 0n, + ch.tokenAddress ?? undefined, + ); userBalance = BigInt(raw.mul(new Decimal(10).pow(dec)).toFixed(0)); } } catch { @@ -483,16 +675,19 @@ export class NitroliteClient { try { const ch = await this.innerClient.getHomeChannel(this.userAddress, asset.symbol); - if (ch.channelId) { + if (ch && ch.channelId) { let userBalance = 0n; try { const state = await this.innerClient.getLatestState(this.userAddress, asset.symbol, false); - const raw = state.homeLedger?.userBalance; + const raw = state?.homeLedger?.userBalance; if (raw) { - const info = this.assetsBySymbol.get(asset.symbol.toLowerCase()); - const dec = info?.decimals ?? asset.decimals; + const dec = await this.getTokenDecimalsForChannel( + asset.symbol, + ch.blockchainId ?? asset.tokens?.[0]?.blockchainId ?? 0n, + ch.tokenAddress || asset.tokens?.[0]?.address, + ); userBalance = BigInt(raw.mul(new Decimal(10).pow(dec)).toFixed(0)); } } catch { @@ -523,17 +718,31 @@ export class NitroliteClient { return channels; } + /** + * Returns nitronode ledger balances encoded with asset-level decimals. + * + * @remarks + * The raw string amounts returned here are not guaranteed to match the raw + * token-unit bigint values expected by on-chain helpers such as deposit(), + * withdrawal(), approveTokens(), getTokenBalance(), or getTokenAllowance(). + * When asset decimals and chain-specific token decimals differ, convert via + * the token helpers instead of passing BigInt(balance.amount) through + * directly to an on-chain method. + */ async getBalances(wallet?: Address): Promise { const balances = await this.innerClient.getBalances(wallet ?? this.userAddress); - return balances.map((b) => { - const info = this.assetsBySymbol.get(b.asset.toLowerCase()); - const dec = info?.decimals ?? 6; - const rawAmount = b.balance.mul(new Decimal(10).pow(dec)).toFixed(0); - return { - asset: b.asset, + const result: LedgerBalance[] = []; + + for (const balance of balances) { + const decimals = await this.getAssetDecimalsForAsset(balance.asset); + const rawAmount = balance.balance.mul(new Decimal(10).pow(decimals)).toFixed(0); + result.push({ + asset: balance.asset, amount: rawAmount, - }; - }); + }); + } + + return result; } async getLedgerEntries(wallet?: Address): Promise { @@ -551,29 +760,33 @@ export class NitroliteClient { } async getAppSessionsList(wallet?: Address, status?: string): Promise { - const mapSessions = (sessions: any[]) => sessions.map((s) => ({ - app_session_id: s.appSessionId, - nonce: Number(s.nonce ?? 0), - participants: s.participants.map((p: any) => p.walletAddress), - protocol: '', - quorum: s.quorum, - status: s.isClosed ? 'closed' : 'open', - version: Number(s.version ?? 0), - weights: s.participants.map((p: any) => p.signatureWeight), - allocations: s.allocations?.map((a: any) => { - const info = this.assetsBySymbol.get(a.asset?.toLowerCase?.() ?? ''); - const dec = info?.decimals ?? 6; - const rawAmount = a.amount - ? a.amount.mul(new Decimal(10).pow(dec)).toFixed(0) - : '0'; - return { - participant: a.participant as Address, - asset: a.asset, - amount: rawAmount, - }; - }) ?? [], - sessionData: s.sessionData ?? '', - })); + const mapSessions = (sessions: any[]) => sessions.map((s) => { + const definition = s.appDefinition ?? { + participants: s.participants ?? [], + quorum: s.quorum, + nonce: s.nonce, + }; + const participants = definition.participants ?? []; + + return { + app_session_id: s.appSessionId, + nonce: Number(definition.nonce ?? 0), + participants: participants.map((p: any) => p.walletAddress), + protocol: '', + quorum: definition.quorum ?? 0, + status: s.isClosed ? 'closed' : 'open', + version: Number(s.version ?? 0), + weights: participants.map((p: any) => p.signatureWeight), + allocations: s.allocations?.map((a: any) => { + return { + participant: a.participant as Address, + asset: a.asset, + amount: a.amount?.toString?.() ?? '0', + }; + }) ?? [], + sessionData: s.sessionData ?? '', + }; + }); const participant = (wallet ?? this.userAddress).toLowerCase() as Address; const normalizedStatus = status?.toLowerCase(); @@ -629,6 +842,9 @@ export class NitroliteClient { return this._lastAppSessionsListError; } + /** @deprecated Returns the legacy ClearNodeAsset shape. Kept for + * backwards compatibility with v0.5.3 callers; new code should + * call the v1 Client's `getAssets()` directly. */ async getAssetsList(): Promise { const assets = await this.innerClient.getAssets(); const result: ClearNodeAsset[] = []; @@ -638,7 +854,7 @@ export class NitroliteClient { token: token.address as Address, chainId: Number(token.blockchainId), symbol: asset.symbol, - decimals: asset.decimals, + decimals: token.decimals, }); } } @@ -657,6 +873,13 @@ export class NitroliteClient { return this.innerClient.signChannelSessionKeyState(state); } + async signChannelSessionKeyOwnership( + state: ChannelSessionKeyStateV1, + sessionKeySigner: EthereumMsgSigner, + ): Promise { + return this.innerClient.signChannelSessionKeyOwnership(state, sessionKeySigner); + } + async submitChannelSessionKeyState(state: ChannelSessionKeyStateV1): Promise { await this.innerClient.submitChannelSessionKeyState(state); } @@ -664,20 +887,39 @@ export class NitroliteClient { async getLastChannelKeyStates( userAddress: string, sessionKey?: string, + options?: { includeInactive?: boolean }, ): Promise { - return this.innerClient.getLastChannelKeyStates(userAddress, sessionKey); + return this.innerClient.getLastChannelKeyStates(userAddress, sessionKey, options); } async signSessionKeyState(state: AppSessionKeyStateV1): Promise { return this.innerClient.signSessionKeyState(state); } + async signAppSessionKeyOwnership( + state: AppSessionKeyStateV1, + sessionKeySigner: EthereumMsgSigner, + ): Promise { + return this.innerClient.signAppSessionKeyOwnership(state, sessionKeySigner); + } + async submitSessionKeyState(state: AppSessionKeyStateV1): Promise { await this.innerClient.submitSessionKeyState(state); } + async getLastAppKeyStates( + userAddress: string, + sessionKey?: string, + options?: { includeInactive?: boolean }, + ): Promise { + return this.innerClient.getLastAppKeyStates(userAddress, sessionKey, options); + } + + /** + * @deprecated Use `getLastAppKeyStates` instead. Retained for 0.5.x callers; will be removed in the next major. + */ async getLastKeyStates(userAddress: string, sessionKey?: string): Promise { - return this.innerClient.getLastKeyStates(userAddress, sessionKey); + return this.getLastAppKeyStates(userAddress, sessionKey); } // ----------------------------------------------------------------------- @@ -744,12 +986,10 @@ export class NitroliteClient { const session = sessions[0]; const v1Allocations: AppAllocationV1[] = []; for (const a of closeAllocations) { - const decimals = await this.getDecimalsForAsset(a.asset); - const humanAmount = new Decimal(a.amount).div(new Decimal(10).pow(decimals)); v1Allocations.push({ participant: a.participant as Address, asset: a.asset, - amount: humanAmount, + amount: new Decimal(a.amount), }); } @@ -767,6 +1007,9 @@ export class NitroliteClient { async getAppDefinition(appSessionId: string): Promise { const def = await this.innerClient.getAppDefinition(appSessionId); + if (!def) { + throw new Error(`app session ${appSessionId} not found`); + } return { protocol: def.applicationId, participants: def.participants.map((p) => p.walletAddress), @@ -808,12 +1051,10 @@ export class NitroliteClient { const v1Allocations: AppAllocationV1[] = []; for (const a of params.allocations) { - const decimals = await this.getDecimalsForAsset(a.asset); - const humanAmount = new Decimal(a.amount).div(new Decimal(10).pow(decimals)); v1Allocations.push({ participant: a.participant as Address, asset: a.asset, - amount: humanAmount, + amount: new Decimal(a.amount), }); } @@ -927,7 +1168,7 @@ export class NitroliteClient { /** Transfers are executed sequentially per allocation and are not atomic; a mid-loop failure leaves prior transfers committed. */ async transfer(destination: Address, allocations: TransferAllocation[]): Promise { for (const alloc of allocations) { - const decimals = await this.getDecimalsForAsset(alloc.asset); + const decimals = await this.getAssetDecimalsForAsset(alloc.asset); const humanAmount = new Decimal(alloc.amount).div(new Decimal(10).pow(decimals)); await this.innerClient.transfer(destination, alloc.asset, humanAmount); } @@ -990,7 +1231,11 @@ export class NitroliteClient { } async getEscrowChannel(escrowChannelId: string): Promise { - return this.innerClient.getEscrowChannel(escrowChannelId); + const channel = await this.innerClient.getEscrowChannel(escrowChannelId); + if (!channel) { + throw new Error(`escrow channel ${escrowChannelId} not found`); + } + return channel; } // ----------------------------------------------------------------------- diff --git a/sdk/ts-compat/src/events.ts b/sdk/ts-compat/src/events.ts index d93fa9a95..17133c5c4 100644 --- a/sdk/ts-compat/src/events.ts +++ b/sdk/ts-compat/src/events.ts @@ -1,15 +1,18 @@ -import type { NitroliteClient } from './client'; -import type { LedgerChannel, LedgerBalance, ClearNodeAsset } from './types'; +import type { NitroliteClient } from './client.js'; +import type { LedgerChannel, LedgerBalance, ClearNodeAsset } from './types.js'; export interface EventPollerCallbacks { onChannelUpdate?: (channels: LedgerChannel[]) => void; onBalanceUpdate?: (balances: LedgerBalance[]) => void; + /** @deprecated Receives the legacy ClearNodeAsset shape. Kept for + * backwards compatibility with v0.5.3 callers; new code should + * poll the v1 SDK's `core.Asset` surface directly. */ onAssetsUpdate?: (assets: ClearNodeAsset[]) => void; onError?: (error: Error) => void; } /** - * Polls the v1.0.0 Client for state changes and dispatches synthetic events + * Polls the v1 Client for state changes and dispatches synthetic events * that match the v0.5.3 push event shapes. */ export class EventPoller { diff --git a/sdk/ts-compat/src/index.ts b/sdk/ts-compat/src/index.ts index 469f76c6e..291b0cb1e 100644 --- a/sdk/ts-compat/src/index.ts +++ b/sdk/ts-compat/src/index.ts @@ -1,15 +1,19 @@ // ============================================================================= // @yellow-org/sdk-compat barrel export // -// Re-exports everything apps previously imported from '@layer-3/nitrolite' -// (v0.5.3) but backed by the v1.0.0+ SDK (@yellow-org/sdk). +// Curated migration-layer exports for apps moving off '@layer-3/nitrolite' +// (v0.5.3) onto the v1 runtime (@yellow-org/sdk). +// +// This barrel preserves selected app-facing surfaces. It is not full package +// parity with the published v0.5.3 package, and it is not a promise that every +// legacy helper here is a runtime-faithful one-to-one v1 protocol mapping. // ============================================================================= // --- Client facade --- -export { NitroliteClient, type NitroliteClientConfig } from './client'; +export { NitroliteClient, type NitroliteClientConfig } from './client.js'; // --- Signers --- -export { WalletStateSigner, createECDSAMessageSigner } from './signers'; +export { WalletStateSigner, createECDSAMessageSigner } from './signers.js'; // --- Auth helpers --- export { @@ -18,7 +22,7 @@ export { createAuthVerifyMessageWithJWT, createEIP712AuthMessageSigner, type AuthRequestParams, -} from './auth'; +} from './auth.js'; // --- App session signing helpers --- export { @@ -30,7 +34,7 @@ export { type CreateAppSessionHashParams, type SubmitAppStateHashAllocation, type SubmitAppStateHashParams, -} from './app-signing'; +} from './app-signing.js'; // --- RPC helpers --- export { @@ -61,7 +65,7 @@ export { createPingMessage, convertRPCToClientChannel, convertRPCToClientState, -} from './rpc'; +} from './rpc.js'; // --- Types --- export { @@ -104,9 +108,9 @@ export { type AuthChallengeResponse, type TransferNotificationResponseParams, type LedgerAccountType, -} from './types'; +} from './types.js'; -// --- Clearnode response types (used by consuming apps' stores) --- +// --- Nitronode response types (used by consuming apps' stores) --- export type { AccountInfo, LedgerChannel, @@ -114,7 +118,7 @@ export type { LedgerEntry, AppSession, ClearNodeAsset, -} from './types'; +} from './types.js'; // --- Errors --- export { @@ -125,13 +129,13 @@ export { NotInitializedError, OngoingStateTransitionError, getUserFacingMessage, -} from './errors'; +} from './errors.js'; // --- Events --- -export { EventPoller, type EventPollerCallbacks } from './events'; +export { EventPoller, type EventPollerCallbacks } from './events.js'; // --- Config --- -export { buildClientOptions, blockchainRPCsFromEnv, type CompatClientConfig } from './config'; +export { buildClientOptions, blockchainRPCsFromEnv, type CompatClientConfig } from './config.js'; // NOTE: SDK classes (Client, ChannelDefaultSigner, etc.) are intentionally NOT // re-exported here. Barrel re-exports from '@yellow-org/sdk' trigger eager diff --git a/sdk/ts-compat/src/rpc.ts b/sdk/ts-compat/src/rpc.ts index 83547ec8d..fa12d0606 100644 --- a/sdk/ts-compat/src/rpc.ts +++ b/sdk/ts-compat/src/rpc.ts @@ -1,35 +1,210 @@ /** * RPC compatibility helpers for v0.5.3 imports. * - * In v0.5.3, hooks used a create-sign-send-parse pattern: + * In v0.5.3, apps used a create-sign-send-parse pattern: * const msg = await createGetChannelsMessage(signer.sign, addr); * const raw = await sendRequest(msg); * const parsed = parseGetChannelsResponse(raw); * - * In the compat layer, most apps should call NitroliteClient methods directly. - * The helpers below keep legacy import sites compiling; most create* helpers are - * lightweight placeholders while parse* helpers normalize response shapes. + * The compat layer keeps that import surface available. Helpers that map + * directly onto a live v1 method emit real v1-compatible payloads inside the + * legacy req/sig envelope. Helpers without an honest one-to-one mapping fail + * fast with migration guidance instead of fabricating fake wire payloads. */ -import type { MessageSigner, RPCResponse, NitroliteRPCMessage } from './types'; +import { Decimal } from 'decimal.js'; +import type { Address } from 'viem'; + +import { + RPCAppStateIntent, + type CloseAppSessionRequestParams, + type CreateAppSessionRequestParams, + type MessageSigner, + type NitroliteRPCMessage, + type RPCChannelStatus, + type RPCResponse, + type RPCAppDefinition, + type RPCAppSessionAllocation, + type SubmitAppStateRequestParams, + type SubmitAppStateRequestParamsV04, +} from './types.js'; // --------------------------------------------------------------------------- -// parseAnyRPCResponse -- pass-through +// parseAnyRPCResponse -- response normalization // --------------------------------------------------------------------------- -export function parseAnyRPCResponse(raw: string): RPCResponse { - try { - const data = JSON.parse(raw); - if (Array.isArray(data)) { - return { requestId: data[1] ?? 0, method: data[2] ?? '', params: data[3] ?? {} }; - } - if (data.res) { - return { requestId: data.res[0] ?? 0, method: data.res[1] ?? '', params: data.res[2] ?? {} }; +type LegacyRPCEnvelope = { + req?: [number, string, unknown, number]; + res?: [number, string, unknown, number?]; + sig?: string; +}; + +function legacyJSONReplacer(key: string, value: unknown): unknown { + if (typeof value === 'bigint') { + return value.toString(); + } + + if ( + (key === 'blockchain_id' || key === 'epoch' || key === 'version' || key === 'nonce') && + (typeof value === 'number' || typeof value === 'bigint') + ) { + return value.toString(); + } + + return value; +} + +function parseEnvelope(raw: string): unknown { + return JSON.parse(raw); +} + +function extractResponsePayload(raw: string): { requestId: number; method: string; payload: any } { + const data = parseEnvelope(raw); + + if (Array.isArray(data)) { + return { + requestId: data[0] ?? 0, + method: data[1] ?? '', + payload: data[2] ?? {}, + }; + } + + if (typeof data === 'object' && data !== null && 'res' in data && Array.isArray((data as LegacyRPCEnvelope).res)) { + const response = (data as LegacyRPCEnvelope).res!; + return { + requestId: response[0] ?? 0, + method: response[1] ?? '', + payload: response[2] ?? {}, + }; + } + + return { requestId: 0, method: '', payload: data }; +} + +function normalizePayloadField(payload: any, ...keys: string[]): T | undefined { + for (const key of keys) { + if (payload && Object.prototype.hasOwnProperty.call(payload, key)) { + return payload[key] as T; } - return { requestId: 0, method: '', params: data }; - } catch { - return { requestId: 0, method: 'error', params: { error: 'parse failed' } }; } + return undefined; +} + +function defaultRequestId(): number { + return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); +} + +function serializeMessage(message: NitroliteRPCMessage): string { + return JSON.stringify(message, legacyJSONReplacer); +} + +function newUnsignedMessage(method: string, params: Record, requestId = defaultRequestId(), timestamp = Date.now()): string { + return serializeMessage( + NitroliteRPC.createRequest({ + requestId, + method, + params, + timestamp, + }), + ); +} + +async function newSignedMessage( + signer: MessageSigner, + method: string, + params: Record, + requestId = defaultRequestId(), + timestamp = Date.now(), +): Promise { + const request = NitroliteRPC.createRequest({ + requestId, + method, + params, + timestamp, + }); + const signed = await NitroliteRPC.signRequestMessage(request, signer); + return serializeMessage(signed); +} + +function missingFieldError(helperName: string, fieldName: string, guidance: string): Error { + return new Error( + `[compat] ${helperName} requires ${fieldName} for the live v1 mapping. ${guidance}`, + ); +} + +function unsupportedHelperError(helperName: string, guidance: string): Error { + return new Error(`[compat] ${helperName} is not supported as a direct v1 RPC helper. ${guidance}`); +} + +function toRPCAppDefinition(definition: RPCAppDefinition): Record { + return { + application_id: definition.application, + participants: definition.participants.map((walletAddress, index) => ({ + wallet_address: walletAddress, + signature_weight: definition.weights[index] ?? 1, + })), + quorum: definition.quorum, + nonce: BigInt(definition.nonce ?? Date.now()), + }; +} + +function toRPCAppAllocations(allocations: RPCAppSessionAllocation[]): Array> { + return allocations.map((allocation) => ({ + participant: allocation.participant, + asset: allocation.asset, + amount: new Decimal(allocation.amount), + })); +} + +function toRPCAppStateIntent(intent: RPCAppStateIntent): number { + switch (intent) { + case RPCAppStateIntent.Deposit: + return 1; + case RPCAppStateIntent.Withdraw: + return 2; + case RPCAppStateIntent.Close: + return 3; + case RPCAppStateIntent.Operate: + default: + return 0; + } +} + +function requireSubmitVersion( + helperName: 'createSubmitAppStateMessage' | 'createCloseAppSessionMessage', + version: number | undefined, +): number { + if (version === undefined) { + throw missingFieldError( + helperName, + 'params.version', + 'Use NitroliteClient.submitAppState(...) / closeAppSession(...) or include the current app-session version explicitly.', + ); + } + return version; +} + +function buildSubmitAppStateParams( + params: SubmitAppStateRequestParams, + intentOverride?: RPCAppStateIntent, + quorumSigsOverride?: string[], +): Record { + const intent = intentOverride ?? ('intent' in params ? params.intent : RPCAppStateIntent.Operate); + const version = requireSubmitVersion( + intentOverride === RPCAppStateIntent.Close ? 'createCloseAppSessionMessage' : 'createSubmitAppStateMessage', + 'version' in params ? params.version : undefined, + ); + + return { + app_state_update: { + app_session_id: params.app_session_id, + intent: toRPCAppStateIntent(intent), + version, + allocations: toRPCAppAllocations(params.allocations), + session_data: params.session_data ?? '', + }, + quorum_sigs: quorumSigsOverride ?? params.quorum_sigs ?? [], + }; } // --------------------------------------------------------------------------- @@ -54,77 +229,296 @@ export const NitroliteRPC = { // create*Message / parse*Response compatibility helpers // --------------------------------------------------------------------------- -const noop = async (..._args: any[]): Promise => - JSON.stringify({ req: [0, 'noop', {}, Date.now()], sig: '0x' }); +export function parseAnyRPCResponse(raw: string): RPCResponse { + try { + const { requestId, method, payload } = extractResponsePayload(raw); + return { requestId, method, params: payload }; + } catch { + return { requestId: 0, method: 'error', params: { error: 'parse failed' } }; + } +} + +export async function createGetChannelsMessage( + _signer: MessageSigner, + participant?: Address, + status?: RPCChannelStatus, + requestId?: number, + timestamp?: number, +): Promise { + if (!participant) { + throw missingFieldError( + 'createGetChannelsMessage', + 'participant', + 'Pass the participant wallet or migrate to NitroliteClient.getChannels().', + ); + } + + return newUnsignedMessage( + 'channels.v1.get_channels', + { + wallet: participant, + ...(status ? { status } : {}), + }, + requestId, + timestamp, + ); +} -export const createGetChannelsMessage = noop; export const parseGetChannelsResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { channels: d?.res?.[2]?.channels ?? [] } }; + const { payload } = extractResponsePayload(raw); + return { params: { channels: normalizePayloadField(payload, 'channels') ?? [] } }; }; -export const createGetLedgerBalancesMessage = noop; +export async function createGetLedgerBalancesMessage( + signer: MessageSigner, + accountId?: string, + requestId?: number, + timestamp?: number, +): Promise { + if (!accountId) { + throw missingFieldError( + 'createGetLedgerBalancesMessage', + 'accountId', + 'Pass the wallet/account address or migrate to NitroliteClient.getBalances().', + ); + } + + return newSignedMessage( + signer, + 'user.v1.get_balances', + { wallet: accountId }, + requestId, + timestamp, + ); +} + export const parseGetLedgerBalancesResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { ledgerBalances: d?.res?.[2]?.ledgerBalances ?? [] } }; + const { payload } = extractResponsePayload(raw); + return { + params: { + ledgerBalances: normalizePayloadField(payload, 'ledgerBalances', 'balances') ?? [], + }, + }; }; export const parseGetLedgerEntriesResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { ledgerEntries: d?.res?.[2]?.ledgerEntries ?? [] } }; + const { payload } = extractResponsePayload(raw); + return { + params: { + ledgerEntries: normalizePayloadField(payload, 'ledgerEntries', 'transactions') ?? [], + }, + }; }; export const parseGetAppSessionsResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { appSessions: d?.res?.[2]?.appSessions ?? [] } }; + const { payload } = extractResponsePayload(raw); + return { + params: { + appSessions: normalizePayloadField(payload, 'appSessions', 'app_sessions') ?? [], + }, + }; }; -export const createTransferMessage = noop; -export const createAppSessionMessage = noop; +export async function createTransferMessage( + _signer: MessageSigner, + _params: unknown, + _requestId?: number, + _timestamp?: number, +): Promise { + throw unsupportedHelperError( + 'createTransferMessage', + 'Use NitroliteClient.transfer(destination, allocations) instead.', + ); +} + +export async function createAppSessionMessage( + signer: MessageSigner, + params: CreateAppSessionRequestParams, + requestId?: number, + timestamp?: number, +): Promise { + return newSignedMessage( + signer, + 'app_sessions.v1.create_app_session', + { + definition: toRPCAppDefinition(params.definition), + session_data: params.session_data ?? JSON.stringify({ allocations: params.allocations }), + quorum_sigs: params.quorum_sigs ?? [], + ...(params.owner_sig ? { owner_sig: params.owner_sig } : {}), + }, + requestId, + timestamp, + ); +} + export const parseCreateAppSessionResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { appSessionId: d?.res?.[2]?.appSessionId ?? '' } }; + const { payload } = extractResponsePayload(raw); + return { + params: { + appSessionId: normalizePayloadField(payload, 'appSessionId', 'app_session_id') ?? '', + version: normalizePayloadField(payload, 'version') ?? '', + status: normalizePayloadField(payload, 'status') ?? '', + }, + }; }; -export const createCloseAppSessionMessage = noop; +export async function createCloseAppSessionMessage( + signer: MessageSigner, + params: CloseAppSessionRequestParams, + requestId?: number, + timestamp?: number, +): Promise { + const version = requireSubmitVersion('createCloseAppSessionMessage', params.version); + + return newSignedMessage( + signer, + 'app_sessions.v1.submit_app_state', + buildSubmitAppStateParams( + { + app_session_id: params.app_session_id, + intent: RPCAppStateIntent.Close, + version, + allocations: params.allocations, + session_data: params.session_data, + quorum_sigs: params.quorum_sigs, + } as SubmitAppStateRequestParamsV04, + RPCAppStateIntent.Close, + params.quorum_sigs, + ), + requestId, + timestamp, + ); +} + export const parseCloseAppSessionResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { appSessionId: d?.res?.[2]?.appSessionId ?? '' } }; + const { payload } = extractResponsePayload(raw); + return { params: payload ?? {} }; }; -export const createSubmitAppStateMessage = noop; +export async function createSubmitAppStateMessage( + signer: MessageSigner, + params: SubmitAppStateRequestParams, + requestId?: number, + timestamp?: number, +): Promise { + return newSignedMessage( + signer, + 'app_sessions.v1.submit_app_state', + buildSubmitAppStateParams(params), + requestId, + timestamp, + ); +} + export const parseSubmitAppStateResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: { appSessionId: d?.res?.[2]?.appSessionId ?? '', version: d?.res?.[2]?.version ?? 0, status: d?.res?.[2]?.status ?? '' } }; + const { payload } = extractResponsePayload(raw); + return { params: payload ?? {} }; }; -export const createGetAppDefinitionMessage = noop; +export async function createGetAppDefinitionMessage( + _signer: MessageSigner, + appSessionId: string, + requestId?: number, + timestamp?: number, +): Promise { + return newUnsignedMessage( + 'app_sessions.v1.get_app_definition', + { app_session_id: appSessionId }, + requestId, + timestamp, + ); +} + export const parseGetAppDefinitionResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: d?.res?.[2] ?? {} }; + const { payload } = extractResponsePayload(raw); + return { params: normalizePayloadField>(payload, 'definition') ?? {} }; }; -export const createGetAppSessionsMessage = noop; +export async function createGetAppSessionsMessage( + _signer: MessageSigner, + participant: Address, + status?: RPCChannelStatus, + requestId?: number, + timestamp?: number, +): Promise { + if (!participant) { + throw missingFieldError( + 'createGetAppSessionsMessage', + 'participant', + 'Pass the participant wallet or migrate to NitroliteClient.getAppSessionsList().', + ); + } + + return newUnsignedMessage( + 'app_sessions.v1.get_app_sessions', + { + participant, + ...(status ? { status } : {}), + }, + requestId, + timestamp, + ); +} + +export async function createCreateChannelMessage( + _signer: MessageSigner, + _params: unknown, + _requestId?: number, + _timestamp?: number, +): Promise { + throw unsupportedHelperError( + 'createCreateChannelMessage', + 'Use NitroliteClient.deposit(tokenAddress, amount) or depositAndCreateChannel(...) instead.', + ); +} -export const createCreateChannelMessage = noop; export const parseCreateChannelResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: d?.res?.[2] ?? {} }; + const { payload } = extractResponsePayload(raw); + return { params: payload ?? {} }; }; -export const createCloseChannelMessage = noop; +export async function createCloseChannelMessage( + _signer: MessageSigner, + _channelId: string, + _fundDestination: Address, + _requestId?: number, + _timestamp?: number, +): Promise { + throw unsupportedHelperError( + 'createCloseChannelMessage', + 'Use NitroliteClient.closeChannel(...) instead.', + ); +} + export const parseCloseChannelResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: d?.res?.[2] ?? {} }; + const { payload } = extractResponsePayload(raw); + return { params: payload ?? {} }; }; -export const createResizeChannelMessage = noop; +export async function createResizeChannelMessage( + _signer: MessageSigner, + _params: unknown, + _requestId?: number, + _timestamp?: number, +): Promise { + throw unsupportedHelperError( + 'createResizeChannelMessage', + 'Use NitroliteClient.resizeChannel(...) instead.', + ); +} + export const parseResizeChannelResponse = (raw: string) => { - const d = JSON.parse(raw); - return { params: d?.res?.[2] ?? {} }; + const { payload } = extractResponsePayload(raw); + return { params: payload ?? {} }; }; -export const createPingMessage = noop; +export async function createPingMessage( + _signer: MessageSigner, + requestId?: number, + timestamp?: number, +): Promise { + return newUnsignedMessage('node.v1.ping', {}, requestId, timestamp); +} export const convertRPCToClientChannel = (ch: any) => ch; export const convertRPCToClientState = (st: any, _sig?: string) => st; diff --git a/sdk/ts-compat/src/signers.ts b/sdk/ts-compat/src/signers.ts index 221494e9f..ddac153c5 100644 --- a/sdk/ts-compat/src/signers.ts +++ b/sdk/ts-compat/src/signers.ts @@ -1,12 +1,12 @@ import { Hex } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; -import type { MessageSigner, MessageSignerPayload } from './types'; +import type { MessageSigner, MessageSignerPayload } from './types.js'; /** * v0.5.3-compatible WalletStateSigner. * In v0.5.3, this wraps a WalletClient and signs EIP-191 messages. * In the compat layer, it is only stored as a reference -- actual signing - * is handled by the v1.0.0 SDK's ChannelDefaultSigner internally. + * is handled by the v1 SDK's ChannelDefaultSigner internally. * We keep the class so that NitroliteStore.state.walletStateSigner compiles. */ export class WalletStateSigner { diff --git a/sdk/ts-compat/src/types.ts b/sdk/ts-compat/src/types.ts index 857f31a7d..83d46c8cb 100644 --- a/sdk/ts-compat/src/types.ts +++ b/sdk/ts-compat/src/types.ts @@ -37,6 +37,7 @@ export enum RPCChannelStatus { Closed = 'closed', Resizing = 'resizing', Challenged = 'challenged', + Closing = 'closing', } export enum RPCProtocolVersion { @@ -48,6 +49,7 @@ export enum RPCAppStateIntent { Operate = 'operate', Deposit = 'deposit', Withdraw = 'withdraw', + Close = 'close', } export enum RPCTxType { @@ -170,6 +172,7 @@ export interface ResizeChannelRequestParams { export interface TransferAllocation { asset: string; + /** Raw asset units using the asset's canonical decimals (for example 5 USDC = "5000000" when USDC has 6 asset decimals). */ amount: string; } @@ -189,6 +192,7 @@ export interface RPCAppDefinition { export interface RPCAppSessionAllocation { asset: string; + /** Human-readable decimal string, matching the legacy app-session APIs (for example "0.01"). */ amount: string; participant: Address; } @@ -283,7 +287,7 @@ export interface AppLogic { } // ============================================================================= -// Clearnode response types (previously in appstore/src/store/types.ts) +// Nitronode response types (previously in appstore/src/store/types.ts) // ============================================================================= export interface AccountInfo { @@ -336,10 +340,14 @@ export interface LedgerEntry { created_at: string; } +/** @deprecated Legacy name from the Clearnode era. Kept for backwards + * compatibility with v0.5.3 callers; new code should use the v1 SDK's + * `core.Asset` instead. */ export interface ClearNodeAsset { token: Address; chainId: number; symbol: string; + /** Token decimals for this specific chain/token entry. */ decimals: number; } diff --git a/sdk/ts-compat/test/unit/__snapshots__/public-api-drift.test.ts.snap b/sdk/ts-compat/test/unit/__snapshots__/public-api-drift.test.ts.snap new file mode 100644 index 000000000..9dc60c883 --- /dev/null +++ b/sdk/ts-compat/test/unit/__snapshots__/public-api-drift.test.ts.snap @@ -0,0 +1,1127 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`compat public runtime API drift guard keeps root TypeScript public API signatures intentional 1`] = ` +[ + { + "declaration": "string", + "kind": "type", + "name": "AccountID", + "type": "string", + }, + { + "kind": "interface", + "name": "AccountInfo", + "properties": [ + "balances: LedgerBalance[]", + "channelCount: bigint", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "Allocation", + "properties": [ + "amount: bigint", + "destination: Address | string", + "token: Address | string", + ], + "signatures": [], + }, + { + "constructors": [ + "(message: string): AllowanceError", + ], + "kind": "class", + "name": "AllowanceError", + "properties": [ + "code: string", + "message: string", + "name: string", + "stack: string", + ], + "staticProperties": [ + "captureStackTrace: (targetObject: object, constructorOpt?: Function): void", + "prepareStackTrace: (err: Error, stackTraces: NodeJS.CallSite[]): any", + "stackTraceLimit: number", + ], + }, + { + "kind": "interface", + "name": "AppLogic", + "properties": [ + "decode: (encoded: Hex): T", + "encode: (data: T): Hex", + "getAdjudicatorAddress: (): Address", + "getAdjudicatorType: (): string", + "isFinal: (state: T): boolean", + "provideProofs: (channel: Channel, state: T, previousStates: State[]): State[]", + "validateTransition: (channel: Channel, prevState: T, nextState: T): boolean", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "AppSession", + "properties": [ + "allocations: RPCAppSessionAllocation[]", + "app_session_id: string", + "nonce: number", + "participants: string[]", + "protocol: string", + "quorum: number", + "sessionData: string", + "status: string", + "version: number", + "weights: number[]", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "AuthChallengeResponse", + "properties": [ + "method: RPCMethod.AuthChallenge", + "params: { challengeMessage: string; }", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "AuthRequestParams", + "properties": [ + "address: string", + "allowances: { asset: string; amount: string }[]", + "application: string", + "expires_at: bigint", + "scope: string", + "session_key: string", + ], + "signatures": [], + }, + { + "kind": "function", + "name": "blockchainRPCsFromEnv", + "signatures": [ + "(): Record", + ], + }, + { + "kind": "function", + "name": "buildClientOptions", + "signatures": [ + "(config: CompatClientConfig): Option[]", + ], + }, + { + "kind": "interface", + "name": "Channel", + "properties": [ + "adjudicator: Address", + "challenge: number", + "channelId: string", + "nonce: bigint", + "participants: Address[]", + "version: bigint", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "ChannelData", + "properties": [ + "lastValidState: any", + "stateData: Hex", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "ClearNodeAsset", + "properties": [ + "chainId: number", + "decimals: number", + "symbol: string", + "token: Address", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "CloseAppSessionRequestParams", + "properties": [ + "allocations: RPCAppSessionAllocation[]", + "app_session_id: string", + "quorum_sigs: string[]", + "session_data: string", + "version: number", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "CloseChannelResponseParams", + "properties": [ + "channelId: string", + "serverSignature: string", + "state: any", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "CompatClientConfig", + "properties": [ + "blockchainRPCs: Record", + "wsURL: string", + ], + "signatures": [], + }, + { + "constructors": [ + "(message: string, code: string): CompatError", + ], + "kind": "class", + "name": "CompatError", + "properties": [ + "code: string", + "message: string", + "name: string", + "stack: string", + ], + "staticProperties": [ + "captureStackTrace: (targetObject: object, constructorOpt?: Function): void", + "prepareStackTrace: (err: Error, stackTraces: NodeJS.CallSite[]): any", + "stackTraceLimit: number", + ], + }, + { + "kind": "interface", + "name": "ContractAddresses", + "properties": [ + "adjudicator: Address | string", + "custody: Address | string", + ], + "signatures": [], + }, + { + "kind": "const", + "name": "convertRPCToClientChannel", + "type": "(ch: any) => any", + }, + { + "kind": "const", + "name": "convertRPCToClientState", + "type": "(st: any, _sig?: string) => any", + }, + { + "kind": "interface", + "name": "CreateAppSessionHashParams", + "properties": [ + "application: string", + "nonce: bigint | number", + "participants: CreateAppSessionHashParticipant[]", + "quorum: number", + "sessionData: string", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "CreateAppSessionHashParticipant", + "properties": [ + "signatureWeight: number", + "walletAddress: Address | Hex", + ], + "signatures": [], + }, + { + "kind": "function", + "name": "createAppSessionMessage", + "signatures": [ + "(signer: MessageSigner, params: CreateAppSessionRequestParams, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "interface", + "name": "CreateAppSessionRequestParams", + "properties": [ + "allocations: RPCAppSessionAllocation[]", + "definition: RPCAppDefinition", + "owner_sig: string", + "quorum_sigs: string[]", + "session_data: string", + ], + "signatures": [], + }, + { + "kind": "function", + "name": "createAuthRequestMessage", + "signatures": [ + "(params: AuthRequestParams, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createAuthVerifyMessage", + "signatures": [ + "(signer: MessageSigner, challenge: { params: { challengeMessage: string; }; }, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createAuthVerifyMessageWithJWT", + "signatures": [ + "(jwtToken: string, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "interface", + "name": "CreateChannelResponseParams", + "properties": [ + "channel: any", + "serverSignature: string", + "state: any", + ], + "signatures": [], + }, + { + "kind": "function", + "name": "createCloseAppSessionMessage", + "signatures": [ + "(signer: MessageSigner, params: CloseAppSessionRequestParams, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createCloseChannelMessage", + "signatures": [ + "(_signer: MessageSigner, _channelId: string, _fundDestination: Address, _requestId?: number, _timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createCreateChannelMessage", + "signatures": [ + "(_signer: MessageSigner, _params: unknown, _requestId?: number, _timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createECDSAMessageSigner", + "signatures": [ + "(privateKey: Hex): MessageSigner", + ], + }, + { + "kind": "function", + "name": "createEIP712AuthMessageSigner", + "signatures": [ + "(walletClient: any, partialMessage: { scope: string; session_key: \`0x\${string}\`; expires_at: bigint; allowances: { asset: string; amount: string; }[]; }, domain: { name: string; }): MessageSigner", + ], + }, + { + "kind": "function", + "name": "createGetAppDefinitionMessage", + "signatures": [ + "(_signer: MessageSigner, appSessionId: string, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createGetAppSessionsMessage", + "signatures": [ + "(_signer: MessageSigner, participant: Address, status?: RPCChannelStatus, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createGetChannelsMessage", + "signatures": [ + "(_signer: MessageSigner, participant?: Address, status?: RPCChannelStatus, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createGetLedgerBalancesMessage", + "signatures": [ + "(signer: MessageSigner, accountId?: string, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createPingMessage", + "signatures": [ + "(_signer: MessageSigner, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createResizeChannelMessage", + "signatures": [ + "(_signer: MessageSigner, _params: unknown, _requestId?: number, _timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createSubmitAppStateMessage", + "signatures": [ + "(signer: MessageSigner, params: SubmitAppStateRequestParams, requestId?: number, timestamp?: number): Promise", + ], + }, + { + "kind": "function", + "name": "createTransferMessage", + "signatures": [ + "(_signer: MessageSigner, _params: unknown, _requestId?: number, _timestamp?: number): Promise", + ], + }, + { + "kind": "const", + "name": "EIP712AuthTypes", + "type": "{ readonly Policy: readonly [{ readonly name: 'challenge'; readonly type: 'string'; }, { readonly name: 'scope'; readonly type: 'string'; }, { readonly name: 'wallet'; readonly type: 'address'; }, { readonly name: 'session_key'; readonly type: 'address'; }, { readonly name: 'expires_at'; readonly type: 'uint64'; }, { readonly name: 'allowances'; readonly type: 'Allowance[]'; }]; readonly Allowance: readonly [{ readonly name: 'asset'; readonly type: 'string'; }, { readonly name: 'amount'; readonly type: 'string'; }]; }", + }, + { + "constructors": [ + "(client: NitroliteClient, callbacks: EventPollerCallbacks, intervalMs?: number): EventPoller", + ], + "kind": "class", + "name": "EventPoller", + "properties": [ + "setInterval: (ms: number): void", + "start: (): void", + "stop: (): void", + ], + "staticProperties": [], + }, + { + "kind": "interface", + "name": "EventPollerCallbacks", + "properties": [ + "onAssetsUpdate: (assets: ClearNodeAsset[]) => void", + "onBalanceUpdate: (balances: LedgerBalance[]) => void", + "onChannelUpdate: (channels: LedgerChannel[]) => void", + "onError: (error: Error) => void", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "FinalState", + "properties": [ + "allocations: [Allocation, Allocation]", + "channelId: string", + "data: Hex", + "intent: number", + "serverSignature: string", + "version: bigint", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "GetAppDefinitionResponseParams", + "properties": [ + "challenge: number", + "nonce: number", + "participants: Address[]", + "protocol: string", + "quorum: number", + "weights: number[]", + ], + "signatures": [], + }, + { + "kind": "function", + "name": "getUserFacingMessage", + "signatures": [ + "(error: unknown): string", + ], + }, + { + "constructors": [ + "(message: string): InsufficientFundsError", + ], + "kind": "class", + "name": "InsufficientFundsError", + "properties": [ + "code: string", + "message: string", + "name: string", + "stack: string", + ], + "staticProperties": [ + "captureStackTrace: (targetObject: object, constructorOpt?: Function): void", + "prepareStackTrace: (err: Error, stackTraces: NodeJS.CallSite[]): any", + "stackTraceLimit: number", + ], + }, + { + "declaration": "Address | \`0x\${string}\`", + "kind": "type", + "name": "LedgerAccountType", + "type": "\`0x\${string}\`", + }, + { + "kind": "interface", + "name": "LedgerBalance", + "properties": [ + "amount: string", + "asset: string", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "LedgerChannel", + "properties": [ + "adjudicator: string", + "amount: bigint", + "chain_id: number", + "challenge: number", + "channel_id: string", + "created_at: string", + "nonce: number", + "participant: string", + "status: string", + "token: string", + "updated_at: string", + "version: number", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "LedgerEntry", + "properties": [ + "account_id: string", + "account_type: number", + "asset: string", + "created_at: string", + "credit: string", + "debit: string", + "id: number", + "participant: string", + ], + "signatures": [], + }, + { + "declaration": "(payload: MessageSignerPayload) => Promise", + "kind": "type", + "name": "MessageSigner", + "type": "MessageSigner", + }, + { + "constructors": [ + "(client: Client, userAddress: Address, chainId: number, walletClient: WalletClient, blockchainRPCs?: Record): NitroliteClient", + ], + "kind": "class", + "name": "NitroliteClient", + "properties": [ + "acknowledge: (tokenAddress: Address): Promise", + "approveSecurityToken: (chainId: number, amount: bigint): Promise", + "approveTokens: (tokenAddress: Address, amount: bigint): Promise", + "cancelSecurityTokensWithdrawal: (chainId: number): Promise", + "challengeChannel: (params: { state: any; }): Promise", + "checkTokenAllowance: (chainId: number, tokenAddress: Address): Promise", + "checkpointChannel: (_params: unknown): Promise", + "close: (): Promise", + "closeAppSession: (appSessionIdOrParams: string | CloseAppSessionRequestParams, allocations?: RPCAppSessionAllocation[], quorumSigs?: string[]): Promise<{ appSessionId: string; }>", + "closeChannel: (params?: { tokenAddress?: Address | string; } | any): Promise", + "createAppSession: (definitionOrParams: RPCAppDefinition | CreateAppSessionRequestParams, allocations?: RPCAppSessionAllocation[], quorumSigs?: string[], opts?: { ownerSig?: string; }): Promise<{ appSessionId: string; version: string; status: string; }>", + "createChannel: (_respParams?: any): Promise", + "deposit: (tokenAddress: Address, amount: bigint): Promise", + "depositAndCreateChannel: (tokenAddress: Address, amount: bigint, _respParams?: any): Promise", + "findOpenChannel: (tokenAddress: Address | string, chainId?: number): LedgerChannel | null", + "formatAmount: (tokenAddress: Address | string, rawAmount: bigint): Promise", + "getAccountBalance: (_tokenAddress: Address | Address[]): Promise", + "getAccountInfo: (): Promise", + "getActionAllowances: (wallet?: Address): Promise", + "getAppDefinition: (appSessionId: string): Promise", + "getAppSessionsList: (wallet?: Address, status?: string): Promise", + "getApps: (options?: { appId?: string; ownerWallet?: string; page?: number; pageSize?: number; }): Promise<{ apps: AppInfoV1[]; metadata: core.PaginationMetadata; }>", + "getAssetsList: (): Promise", + "getBalances: (wallet?: Address): Promise", + "getBlockchains: (): Promise", + "getChannelBalance: (_channelId: string, _tokenAddress: Address | Address[]): Promise", + "getChannelData: (_channelId: string): Promise", + "getChannels: (): Promise", + "getConfig: (): Promise", + "getEscrowChannel: (escrowChannelId: string): Promise", + "getLastAppKeyStates: (userAddress: string, sessionKey?: string, options?: { includeInactive?: boolean; }): Promise", + "getLastAppSessionsListError: (): string | null", + "getLastChannelKeyStates: (userAddress: string, sessionKey?: string, options?: { includeInactive?: boolean; }): Promise", + "getLastKeyStates: (userAddress: string, sessionKey?: string): Promise", + "getLedgerEntries: (wallet?: Address): Promise", + "getLockedBalance: (chainId: number, wallet?: Address): Promise", + "getOpenChannels: (): Promise", + "getTokenAllowance: (tokenAddress: Address): Promise", + "getTokenBalance: (tokenAddress: Address): Promise", + "getTokenDecimals: (tokenAddress: Address | string): Promise", + "initiateSecurityTokensWithdrawal: (chainId: number): Promise", + "innerClient: Client", + "lockSecurityTokens: (targetWallet: Address, chainId: number, amount: bigint): Promise", + "parseAmount: (tokenAddress: Address | string, humanAmount: string): Promise", + "ping: (): Promise", + "refreshAssets: (): Promise", + "registerApp: (appID: string, metadata: string, creationApprovalNotRequired: boolean): Promise", + "resizeChannel: (params: { allocate_amount: bigint; token: Address; }): Promise", + "resolveAsset: (symbol: string): Promise", + "resolveAssetDisplay: (tokenAddress: Address | string, _chainId?: number): Promise<{ symbol: string; decimals: number; } | null>", + "resolveToken: (tokenAddress: Address | string): Promise", + "signAppSessionKeyOwnership: (state: AppSessionKeyStateV1, sessionKeySigner: EthereumMsgSigner): Promise", + "signChannelSessionKeyOwnership: (state: ChannelSessionKeyStateV1, sessionKeySigner: EthereumMsgSigner): Promise", + "signChannelSessionKeyState: (state: ChannelSessionKeyStateV1): Promise", + "signSessionKeyState: (state: AppSessionKeyStateV1): Promise", + "submitAppState: (params: SubmitAppStateRequestParams): Promise<{ appSessionId: string; version: number; status: string; }>", + "submitChannelSessionKeyState: (state: ChannelSessionKeyStateV1): Promise", + "submitSessionKeyState: (state: AppSessionKeyStateV1): Promise", + "transfer: (destination: Address, allocations: TransferAllocation[]): Promise", + "userAddress: Address", + "waitForClose: (): Promise", + "withdrawSecurityTokens: (chainId: number, destination: Address): Promise", + "withdrawal: (tokenAddress: Address, amount: bigint): Promise", + ], + "staticProperties": [ + "classifyError: (error: unknown): Error", + "create: (config: NitroliteClientConfig): Promise", + ], + }, + { + "kind": "interface", + "name": "NitroliteClientConfig", + "properties": [ + "addresses: ContractAddresses", + "blockchainRPCs: Record", + "chainId: number", + "challengeDuration: bigint", + "channelSessionKeySigner: { sessionKeyPrivateKey: Hex; walletAddress: Address; metadataHash: Hex; authSig: Hex; }", + "walletClient: WalletClient", + "wsURL: string", + ], + "signatures": [], + }, + { + "kind": "const", + "name": "NitroliteRPC", + "type": "{ createRequest(opts: { requestId: number; method: string; params: any; timestamp: number; }): NitroliteRPCMessage; signRequestMessage(msg: NitroliteRPCMessage, signer: MessageSigner): Promise; }", + }, + { + "kind": "interface", + "name": "NitroliteRPCMessage", + "properties": [ + "req: NitroliteRPCRequest", + "sig: string", + ], + "signatures": [], + }, + { + "constructors": [ + "(message: string): NotInitializedError", + ], + "kind": "class", + "name": "NotInitializedError", + "properties": [ + "code: string", + "message: string", + "name: string", + "stack: string", + ], + "staticProperties": [ + "captureStackTrace: (targetObject: object, constructorOpt?: Function): void", + "prepareStackTrace: (err: Error, stackTraces: NodeJS.CallSite[]): any", + "stackTraceLimit: number", + ], + }, + { + "constructors": [ + "(message: string): OngoingStateTransitionError", + ], + "kind": "class", + "name": "OngoingStateTransitionError", + "properties": [ + "code: string", + "message: string", + "name: string", + "stack: string", + ], + "staticProperties": [ + "captureStackTrace: (targetObject: object, constructorOpt?: Function): void", + "prepareStackTrace: (err: Error, stackTraces: NodeJS.CallSite[]): any", + "stackTraceLimit: number", + ], + }, + { + "kind": "function", + "name": "packCreateAppSessionHash", + "signatures": [ + "(params: CreateAppSessionHashParams): Hex", + ], + }, + { + "kind": "function", + "name": "packSubmitAppStateHash", + "signatures": [ + "(params: SubmitAppStateHashParams): Hex", + ], + }, + { + "kind": "function", + "name": "parseAnyRPCResponse", + "signatures": [ + "(raw: string): RPCResponse", + ], + }, + { + "kind": "const", + "name": "parseCloseAppSessionResponse", + "type": "(raw: string) => { params: any; }", + }, + { + "kind": "const", + "name": "parseCloseChannelResponse", + "type": "(raw: string) => { params: any; }", + }, + { + "kind": "const", + "name": "parseCreateAppSessionResponse", + "type": "(raw: string) => { params: { appSessionId: string; version: string | number; status: string; }; }", + }, + { + "kind": "const", + "name": "parseCreateChannelResponse", + "type": "(raw: string) => { params: any; }", + }, + { + "kind": "const", + "name": "parseGetAppDefinitionResponse", + "type": "(raw: string) => { params: Record; }", + }, + { + "kind": "const", + "name": "parseGetAppSessionsResponse", + "type": "(raw: string) => { params: { appSessions: Array; }; }", + }, + { + "kind": "const", + "name": "parseGetChannelsResponse", + "type": "(raw: string) => { params: { channels: Array; }; }", + }, + { + "kind": "const", + "name": "parseGetLedgerBalancesResponse", + "type": "(raw: string) => { params: { ledgerBalances: Array; }; }", + }, + { + "kind": "const", + "name": "parseGetLedgerEntriesResponse", + "type": "(raw: string) => { params: { ledgerEntries: Array; }; }", + }, + { + "kind": "const", + "name": "parseResizeChannelResponse", + "type": "(raw: string) => { params: any; }", + }, + { + "kind": "const", + "name": "parseSubmitAppStateResponse", + "type": "(raw: string) => { params: any; }", + }, + { + "declaration": "number", + "kind": "type", + "name": "RequestID", + "type": "number", + }, + { + "kind": "interface", + "name": "ResizeChannelRequestParams", + "properties": [ + "allocate_amount: bigint", + "channel_id: string", + "funds_destination: Address | string", + "resize_amount: bigint", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "RPCAppDefinition", + "properties": [ + "application: string", + "challenge: number", + "nonce: number", + "participants: Hex[]", + "protocol: RPCProtocolVersion", + "quorum: number", + "weights: number[]", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "RPCAppSession", + "properties": [ + "appSessionId: Hex", + "application: string", + "challenge: number", + "createdAt: Date", + "nonce: number", + "participants: Address[]", + "protocol: RPCProtocolVersion", + "quorum: number", + "sessionData: string", + "status: RPCChannelStatus", + "updatedAt: Date", + "version: number", + "weights: number[]", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "RPCAppSessionAllocation", + "properties": [ + "amount: string", + "asset: string", + "participant: Address", + ], + "signatures": [], + }, + { + "kind": "enum", + "members": [ + "Operate = 'operate'", + "Deposit = 'deposit'", + "Withdraw = 'withdraw'", + "Close = 'close'", + ], + "name": "RPCAppStateIntent", + }, + { + "kind": "interface", + "name": "RPCAsset", + "properties": [ + "chainId: number", + "decimals: number", + "symbol: string", + "token: Address", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "RPCBalance", + "properties": [ + "amount: string", + "asset: string", + ], + "signatures": [], + }, + { + "kind": "enum", + "members": [ + "Open = 'open'", + "Closed = 'closed'", + "Resizing = 'resizing'", + "Challenged = 'challenged'", + "Closing = 'closing'", + ], + "name": "RPCChannelStatus", + }, + { + "kind": "interface", + "name": "RPCChannelUpdate", + "properties": [ + "adjudicator: string", + "amount: bigint", + "chainId: number", + "challenge: number", + "channelId: string", + "createdAt: string", + "nonce: number", + "participant: string", + "status: string", + "token: string", + "updatedAt: string", + "version: number", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "RPCLedgerEntry", + "properties": [ + "account_id: string", + "account_type: number", + "asset: string", + "created_at: string", + "credit: string", + "debit: string", + "id: number", + "participant: string", + ], + "signatures": [], + }, + { + "kind": "enum", + "members": [ + "Ping = 'ping'", + "GetConfig = 'get_config'", + "GetChannels = 'get_channels'", + "ChannelsUpdate = 'channels_update'", + "ChannelUpdate = 'channel_update'", + "BalanceUpdate = 'balance_update'", + "GetAssets = 'get_assets'", + "Assets = 'assets'", + "GetLedgerBalances = 'get_ledger_balances'", + "GetLedgerEntries = 'get_ledger_entries'", + "GetAppSessions = 'get_app_sessions'", + "CreateChannel = 'create_channel'", + "CloseChannel = 'close_channel'", + "ResizeChannel = 'resize_channel'", + "Transfer = 'transfer'", + "CreateAppSession = 'create_app_session'", + "CloseAppSession = 'close_app_session'", + "SubmitAppState = 'submit_app_state'", + "GetAppDefinition = 'get_app_definition'", + "AuthRequest = 'auth_request'", + "AuthChallenge = 'auth_challenge'", + "AuthVerify = 'auth_verify'", + "Error = 'error'", + "GetLedgerTransactions = 'get_ledger_transactions'", + "TransferNotification = 'tr'", + ], + "name": "RPCMethod", + }, + { + "kind": "enum", + "members": [ + "NitroRPC_0_2 = 'NitroRPC/0.2'", + "NitroRPC_0_4 = 'NitroRPC/0.4'", + ], + "name": "RPCProtocolVersion", + }, + { + "kind": "interface", + "name": "RPCResponse", + "properties": [ + "method: string", + "params: any", + "requestId: number", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "RPCTransaction", + "properties": [ + "amount: string", + "asset: string", + "createdAt: Date", + "fromAccount: LedgerAccountType", + "fromAccountTag: string", + "id: number", + "toAccount: LedgerAccountType", + "toAccountTag: string", + "txType: RPCTxType", + ], + "signatures": [], + }, + { + "kind": "enum", + "members": [ + "Transfer = 'transfer'", + "Deposit = 'deposit'", + "Withdrawal = 'withdrawal'", + "AppDeposit = 'app_deposit'", + "AppWithdrawal = 'app_withdrawal'", + "EscrowLock = 'escrow_lock'", + "EscrowUnlock = 'escrow_unlock'", + ], + "name": "RPCTxType", + }, + { + "kind": "interface", + "name": "State", + "properties": [ + "allocations: Allocation[]", + "channelId: string", + "data: Hex", + "version: bigint", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "SubmitAppStateHashAllocation", + "properties": [ + "amount: string", + "asset: string", + "participant: Address | Hex", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "SubmitAppStateHashParams", + "properties": [ + "allocations: SubmitAppStateHashAllocation[]", + "appSessionId: Hex | string", + "intent: RPCAppStateIntent | 'close' | number", + "sessionData: string", + "version: bigint | number", + ], + "signatures": [], + }, + { + "declaration": "SubmitAppStateRequestParamsV02 | SubmitAppStateRequestParamsV04", + "kind": "type", + "name": "SubmitAppStateRequestParams", + "type": "SubmitAppStateRequestParams", + }, + { + "kind": "interface", + "name": "SubmitAppStateRequestParamsV02", + "properties": [ + "allocations: RPCAppSessionAllocation[]", + "app_session_id: Hex", + "quorum_sigs: string[]", + "session_data: string", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "SubmitAppStateRequestParamsV04", + "properties": [ + "allocations: RPCAppSessionAllocation[]", + "app_session_id: Hex", + "intent: RPCAppStateIntent", + "quorum_sigs: string[]", + "session_data: string", + "version: number", + ], + "signatures": [], + }, + { + "kind": "function", + "name": "toSessionKeyQuorumSignature", + "signatures": [ + "(signature: Hex | string): Hex", + ], + }, + { + "kind": "function", + "name": "toWalletQuorumSignature", + "signatures": [ + "(signature: Hex | string): Hex", + ], + }, + { + "kind": "interface", + "name": "TransferAllocation", + "properties": [ + "amount: string", + "asset: string", + ], + "signatures": [], + }, + { + "kind": "interface", + "name": "TransferNotificationResponseParams", + "properties": [ + "transactions: RPCTransaction[]", + ], + "signatures": [], + }, + { + "constructors": [ + "(message: string): UserRejectedError", + ], + "kind": "class", + "name": "UserRejectedError", + "properties": [ + "code: string", + "message: string", + "name: string", + "stack: string", + ], + "staticProperties": [ + "captureStackTrace: (targetObject: object, constructorOpt?: Function): void", + "prepareStackTrace: (err: Error, stackTraces: NodeJS.CallSite[]): any", + "stackTraceLimit: number", + ], + }, + { + "constructors": [ + "(walletClient: any): WalletStateSigner", + ], + "kind": "class", + "name": "WalletStateSigner", + "properties": [ + "address: Hex", + "sign: (data: Uint8Array): Promise", + ], + "staticProperties": [], + }, +] +`; + +exports[`compat public runtime API drift guard keeps root runtime exports intentional 1`] = ` +[ + "AllowanceError", + "CompatError", + "EIP712AuthTypes", + "EventPoller", + "InsufficientFundsError", + "NitroliteClient", + "NitroliteRPC", + "NotInitializedError", + "OngoingStateTransitionError", + "RPCAppStateIntent", + "RPCChannelStatus", + "RPCMethod", + "RPCProtocolVersion", + "RPCTxType", + "UserRejectedError", + "WalletStateSigner", + "blockchainRPCsFromEnv", + "buildClientOptions", + "convertRPCToClientChannel", + "convertRPCToClientState", + "createAppSessionMessage", + "createAuthRequestMessage", + "createAuthVerifyMessage", + "createAuthVerifyMessageWithJWT", + "createCloseAppSessionMessage", + "createCloseChannelMessage", + "createCreateChannelMessage", + "createECDSAMessageSigner", + "createEIP712AuthMessageSigner", + "createGetAppDefinitionMessage", + "createGetAppSessionsMessage", + "createGetChannelsMessage", + "createGetLedgerBalancesMessage", + "createPingMessage", + "createResizeChannelMessage", + "createSubmitAppStateMessage", + "createTransferMessage", + "getUserFacingMessage", + "packCreateAppSessionHash", + "packSubmitAppStateHash", + "parseAnyRPCResponse", + "parseCloseAppSessionResponse", + "parseCloseChannelResponse", + "parseCreateAppSessionResponse", + "parseCreateChannelResponse", + "parseGetAppDefinitionResponse", + "parseGetAppSessionsResponse", + "parseGetChannelsResponse", + "parseGetLedgerBalancesResponse", + "parseGetLedgerEntriesResponse", + "parseResizeChannelResponse", + "parseSubmitAppStateResponse", + "toSessionKeyQuorumSignature", + "toWalletQuorumSignature", +] +`; diff --git a/sdk/ts-compat/test/unit/amount-semantics.test.ts b/sdk/ts-compat/test/unit/amount-semantics.test.ts new file mode 100644 index 000000000..1e6a90ce1 --- /dev/null +++ b/sdk/ts-compat/test/unit/amount-semantics.test.ts @@ -0,0 +1,60 @@ +import { NitroliteClient } from '../../src/index.js'; + +const CURRENT_CHAIN = 84532n; +const CURRENT_TOKEN = '0x0000000000000000000000000000000000000b01'; + +function makeCompatClient() { + const client = Object.create(NitroliteClient.prototype) as NitroliteClient & Record; + Object.assign(client, { + innerClient: { + getAssets: async () => ([ + { + name: 'Yellow USD', + symbol: 'yusd', + decimals: 6, + suggestedBlockchainId: CURRENT_CHAIN, + tokens: [ + { + name: 'Yellow USD', + symbol: 'YUSD', + address: CURRENT_TOKEN, + blockchainId: CURRENT_CHAIN, + decimals: 8, + }, + ], + }, + ]), + }, + userAddress: '0x00000000000000000000000000000000000000a1', + walletClient: { + chain: { + rpcUrls: { + public: { http: ['https://rpc.base-sepolia.example'] }, + default: { http: ['https://rpc.base-sepolia.example'] }, + }, + }, + }, + assetsByChainAndToken: new Map(), + assetsByToken: new Map(), + assetsBySymbol: new Map(), + _chainId: CURRENT_CHAIN, + _lastChannels: [], + _lastAppSessionsListError: null, + _lastAppSessionsListErrorLogged: null, + _blockchains: [], + _lockingTokenDecimals: new Map(), + _blockchainRPCs: { 84532: 'https://rpc.base-sepolia.example' }, + _publicClients: new Map(), + }); + return client; +} + +describe('compat amount semantics', () => { + it('formats and parses raw token amounts using token decimals', async () => { + const client = makeCompatClient(); + await client.refreshAssets(); + + await expect(client.formatAmount(CURRENT_TOKEN, 123456789n)).resolves.toBe('1.23456789'); + await expect(client.parseAmount(CURRENT_TOKEN, '1.23456789')).resolves.toBe(123456789n); + }); +}); diff --git a/sdk/ts-compat/test/unit/client-mapping.test.ts b/sdk/ts-compat/test/unit/client-mapping.test.ts new file mode 100644 index 000000000..38cfdc98a --- /dev/null +++ b/sdk/ts-compat/test/unit/client-mapping.test.ts @@ -0,0 +1,372 @@ +import { jest } from '@jest/globals'; +import { Decimal } from 'decimal.js'; + +import { NitroliteClient, RPCAppStateIntent } from '../../src/index.js'; + +const USER = '0x00000000000000000000000000000000000000a1'; +const CURRENT_CHAIN = 84532n; +const CURRENT_TOKEN = '0x0000000000000000000000000000000000000b01'; +const OTHER_CHAIN_TOKEN = '0x0000000000000000000000000000000000000b02'; + +function makeAssetsFixture() { + return [ + { + name: 'Yellow USD', + symbol: 'yusd', + decimals: 6, + suggestedBlockchainId: CURRENT_CHAIN, + tokens: [ + { + name: 'Yellow USD', + symbol: 'YUSD', + address: CURRENT_TOKEN, + blockchainId: CURRENT_CHAIN, + decimals: 8, + }, + { + name: 'Yellow USD', + symbol: 'YUSD', + address: OTHER_CHAIN_TOKEN, + blockchainId: 11155111n, + decimals: 6, + }, + ], + }, + ]; +} + +function makeInnerClient(overrides: Record = {}) { + return { + getAssets: jest.fn().mockResolvedValue(makeAssetsFixture()), + getBalances: jest.fn().mockResolvedValue([]), + getChannels: jest.fn().mockResolvedValue({ channels: [] }), + getLatestState: jest.fn(), + getHomeChannel: jest.fn(), + getAppSessions: jest.fn().mockResolvedValue({ sessions: [] }), + submitAppState: jest.fn().mockResolvedValue(undefined), + submitAppSessionDeposit: jest.fn().mockResolvedValue('0xdeposit'), + transfer: jest.fn().mockResolvedValue(undefined), + setHomeBlockchain: jest.fn().mockResolvedValue(undefined), + deposit: jest.fn().mockResolvedValue(undefined), + withdraw: jest.fn().mockResolvedValue(undefined), + closeHomeChannel: jest.fn().mockResolvedValue(undefined), + checkpoint: jest.fn().mockResolvedValue('0xcheckpoint'), + approveToken: jest.fn().mockResolvedValue('0xapprove'), + checkTokenAllowance: jest.fn().mockResolvedValue(77n), + getOnChainBalance: jest.fn().mockResolvedValue(new Decimal('5')), + ping: jest.fn().mockResolvedValue(undefined), + close: jest.fn().mockResolvedValue(undefined), + waitForClose: jest.fn().mockResolvedValue(undefined), + acknowledge: jest.fn().mockResolvedValue(undefined), + getBlockchains: jest.fn().mockResolvedValue([ + { + id: CURRENT_CHAIN, + name: 'Base Sepolia', + channelHubAddress: '0x0000000000000000000000000000000000000c01', + blockStep: 0n, + }, + ]), + ...overrides, + }; +} + +function makeCompatClient(innerOverrides: Record = {}) { + const innerClient = makeInnerClient(innerOverrides); + const client = Object.create(NitroliteClient.prototype) as NitroliteClient & Record; + Object.assign(client, { + innerClient, + userAddress: USER, + walletClient: { + chain: { + id: Number(CURRENT_CHAIN), + rpcUrls: { + public: { http: ['https://rpc.base-sepolia.example'] }, + default: { http: ['https://rpc.base-sepolia.example'] }, + }, + }, + }, + assetsByChainAndToken: new Map(), + assetsByToken: new Map(), + assetsBySymbol: new Map(), + _chainId: CURRENT_CHAIN, + _lastChannels: [], + _lastAppSessionsListError: null, + _lastAppSessionsListErrorLogged: null, + _blockchains: [ + { + id: CURRENT_CHAIN, + name: 'Base Sepolia', + channelHubAddress: '0x0000000000000000000000000000000000000c01', + blockStep: 0n, + }, + ], + _lockingTokenDecimals: new Map(), + _blockchainRPCs: { 84532: 'https://rpc.base-sepolia.example' }, + _publicClients: new Map(), + }); + return { client, innerClient }; +} + +describe('NitroliteClient compat mappings', () => { + it('stores token decimals for token-facing helpers and asset decimals for ledger balance conversions', async () => { + const { client, innerClient } = makeCompatClient({ + getBalances: jest.fn().mockResolvedValue([{ asset: 'yusd', balance: new Decimal('1.23') }]), + getChannels: jest.fn().mockResolvedValue({ + channels: [ + { + channelId: 'channel-1', + userWallet: USER, + status: 1, + asset: 'yusd', + tokenAddress: CURRENT_TOKEN, + blockchainId: CURRENT_CHAIN, + challengeDuration: 86400, + nonce: 1n, + stateVersion: 2n, + }, + ], + }), + getLatestState: jest.fn().mockResolvedValue({ + homeLedger: { userBalance: new Decimal('1.23') }, + }), + }); + + await client.refreshAssets(); + + expect(await client.getTokenDecimals(CURRENT_TOKEN)).toBe(8); + expect(await client.getBalances()).toEqual([{ asset: 'yusd', amount: '1230000' }]); + expect(await client.getChannels()).toEqual([ + expect.objectContaining({ + channel_id: 'channel-1', + amount: 123000000n, + chain_id: 84532, + }), + ]); + expect(innerClient.getLatestState).toHaveBeenCalled(); + }); + + it('keeps app-session allocation amounts in human decimals for list/read/update flows', async () => { + const { client, innerClient } = makeCompatClient({ + getAppSessions: jest.fn() + .mockResolvedValueOnce({ + sessions: [ + { + appSessionId: 'session-1', + nonce: 1n, + participants: [{ walletAddress: USER, signatureWeight: 1 }], + quorum: 1, + isClosed: false, + version: 4n, + allocations: [{ participant: USER, asset: 'yusd', amount: new Decimal('0.01') }], + sessionData: '{"turn":1}', + }, + ], + }) + .mockResolvedValueOnce({ + sessions: [ + { + appSessionId: 'session-2', + version: 6n, + allocations: [], + }, + ], + }) + .mockResolvedValueOnce({ + sessions: [ + { + appSessionId: 'session-3', + version: 8n, + allocations: [], + }, + ], + }), + }); + + await client.refreshAssets(); + + await expect(client.getAppSessionsList()).resolves.toEqual([ + expect.objectContaining({ + app_session_id: 'session-1', + allocations: [{ participant: USER, asset: 'yusd', amount: '0.01' }], + }), + ]); + + await client.closeAppSession('session-2', [{ participant: USER, asset: 'yusd', amount: '1.5' }], ['0xclose']); + expect(innerClient.submitAppState).toHaveBeenCalledWith( + expect.objectContaining({ + appSessionId: 'session-2', + version: 7n, + allocations: [ + expect.objectContaining({ + asset: 'yusd', + amount: expect.any(Decimal), + }), + ], + }), + ['0xclose'], + ); + expect(innerClient.submitAppState.mock.calls[0][0].allocations[0].amount.toString()).toBe('1.5'); + + await client.submitAppState({ + app_session_id: 'session-3', + intent: RPCAppStateIntent.Operate, + version: 9, + allocations: [{ participant: USER, asset: 'yusd', amount: '0.25' }], + quorum_sigs: ['0xoperate'], + session_data: '{"turn":2}', + }); + expect(innerClient.submitAppState.mock.calls[1][0].allocations[0].amount.toString()).toBe('0.25'); + }); + + it('uses asset decimals for transfers and token decimals for token-facing helpers', async () => { + const { client, innerClient } = makeCompatClient(); + + await client.refreshAssets(); + + await client.transfer('0x00000000000000000000000000000000000000d1', [ + { asset: 'yusd', amount: '5000000' }, + ]); + expect(innerClient.transfer).toHaveBeenCalledWith( + '0x00000000000000000000000000000000000000d1', + 'yusd', + expect.any(Decimal), + ); + expect(innerClient.transfer.mock.calls[0][2].toString()).toBe('5'); + + await expect(client.approveTokens(CURRENT_TOKEN, 250000000n)).resolves.toBe('0xapprove'); + expect(innerClient.approveToken).toHaveBeenCalledWith(CURRENT_CHAIN, 'yusd', expect.any(Decimal)); + expect(innerClient.approveToken.mock.calls[0][2].toString()).toBe('2.5'); + + await expect(client.getTokenAllowance(CURRENT_TOKEN)).resolves.toBe(77n); + expect(innerClient.checkTokenAllowance).toHaveBeenCalledWith(CURRENT_CHAIN, CURRENT_TOKEN, USER); + + await expect(client.getTokenBalance(CURRENT_TOKEN)).resolves.toBe(500000000n); + expect(innerClient.getOnChainBalance).toHaveBeenCalledWith(CURRENT_CHAIN, 'yusd', USER); + }); + + it('rounds down on-chain decimal balances to token precision before converting to raw units', async () => { + const { client, innerClient } = makeCompatClient({ + getOnChainBalance: jest.fn().mockResolvedValue(new Decimal('5.123456789')), + }); + + await client.refreshAssets(); + + await expect(client.getTokenBalance(CURRENT_TOKEN)).resolves.toBe(512345678n); + expect(innerClient.getOnChainBalance).toHaveBeenCalledWith(CURRENT_CHAIN, 'yusd', USER); + }); + + it('does not reuse the connected wallet rpc url for a different requested chain', () => { + const { client } = makeCompatClient(); + Object.assign(client, { + _blockchainRPCs: {}, + walletClient: { + chain: { + id: 11155111, + rpcUrls: { + public: { http: ['https://rpc.sepolia.example'] }, + default: { http: ['https://rpc.sepolia.example'] }, + }, + }, + }, + }); + + expect(() => (client as unknown as Record string>).getRPCUrl(84532)).toThrow( + '[compat] No RPC URL configured for chain 84532.', + ); + }); + + it('exposes token-and-chain specific display data and assets list entries', async () => { + const { client } = makeCompatClient(); + + await client.refreshAssets(); + + await expect(client.resolveAssetDisplay(CURRENT_TOKEN)).resolves.toEqual({ symbol: 'yusd', decimals: 8 }); + await expect(client.resolveAssetDisplay(OTHER_CHAIN_TOKEN, 11155111)).resolves.toEqual({ symbol: 'yusd', decimals: 6 }); + await expect(client.getAssetsList()).resolves.toEqual([ + { token: CURRENT_TOKEN, chainId: 84532, symbol: 'yusd', decimals: 8 }, + { token: OTHER_CHAIN_TOKEN, chainId: 11155111, symbol: 'yusd', decimals: 6 }, + ]); + }); + + it.each([ + { numeric: 0, expected: 'void' }, + { numeric: 1, expected: 'open' }, + { numeric: 2, expected: 'challenged' }, + { numeric: 3, expected: 'closing' }, + { numeric: 4, expected: 'closed' }, + ])('maps channel status $numeric to "$expected" in getChannels()', async ({ numeric, expected }) => { + const { client } = makeCompatClient({ + getChannels: jest.fn().mockResolvedValue({ + channels: [ + { + channelId: 'channel-1', + userWallet: USER, + status: numeric, + asset: 'yusd', + tokenAddress: CURRENT_TOKEN, + blockchainId: CURRENT_CHAIN, + challengeDuration: 86400, + nonce: 1n, + stateVersion: 1n, + }, + ], + }), + getLatestState: jest.fn().mockResolvedValue({ + homeLedger: { userBalance: new Decimal('0') }, + }), + }); + await client.refreshAssets(); + const channels = await client.getChannels(); + expect(channels[0].status).toBe(expected); + }); + + it('forwards includeInactive on session-key list methods through to the inner SDK', async () => { + const sessionKey = '0x0000000000000000000000000000000000000d01'; + const { client, innerClient } = makeCompatClient({ + getLastChannelKeyStates: jest.fn().mockResolvedValue([]), + getLastAppKeyStates: jest.fn().mockResolvedValue([]), + }); + + await client.getLastChannelKeyStates(USER, sessionKey, { includeInactive: true }); + await client.getLastAppKeyStates(USER, sessionKey, { includeInactive: true }); + + expect(innerClient.getLastChannelKeyStates).toHaveBeenCalledWith(USER, sessionKey, { + includeInactive: true, + }); + expect(innerClient.getLastAppKeyStates).toHaveBeenCalledWith(USER, sessionKey, { + includeInactive: true, + }); + + await client.getLastChannelKeyStates(USER); + await client.getLastAppKeyStates(USER); + + expect(innerClient.getLastChannelKeyStates).toHaveBeenLastCalledWith(USER, undefined, undefined); + expect(innerClient.getLastAppKeyStates).toHaveBeenLastCalledWith(USER, undefined, undefined); + }); + + it('keeps the deprecated getLastKeyStates alias forwarding to getLastAppKeyStates', async () => { + const sessionKey = '0x0000000000000000000000000000000000000d02'; + const { client, innerClient } = makeCompatClient({ + getLastAppKeyStates: jest.fn().mockResolvedValue([]), + }); + + await client.getLastKeyStates(USER, sessionKey); + expect(innerClient.getLastAppKeyStates).toHaveBeenCalledWith(USER, sessionKey, undefined); + + await client.getLastKeyStates(USER); + expect(innerClient.getLastAppKeyStates).toHaveBeenLastCalledWith(USER, undefined, undefined); + }); + + it('keeps unsupported legacy methods honest and getOpenChannels delegates to the current chain hub', async () => { + const { client } = makeCompatClient(); + const readContract = jest.fn().mockResolvedValue(['0xabc', '0xdef']); + (client as unknown as Record).getReadClientForChain = jest.fn().mockReturnValue({ readContract }); + + await expect(client.getOpenChannels()).resolves.toEqual(['0xabc', '0xdef']); + + await expect(client.createChannel()).rejects.toThrow('deposit(tokenAddress, amount)'); + await expect(client.checkpointChannel({})).rejects.toThrow('client.innerClient.checkpoint(asset)'); + await expect(client.getAccountBalance(CURRENT_TOKEN)).rejects.toThrow('Use getBalances()'); + await expect(client.getChannelBalance('channel-1', CURRENT_TOKEN)).rejects.toThrow('Use getChannelData(channelId)'); + }); +}); diff --git a/sdk/ts-compat/test/unit/client.test.ts b/sdk/ts-compat/test/unit/client.test.ts new file mode 100644 index 000000000..9f18c94dd --- /dev/null +++ b/sdk/ts-compat/test/unit/client.test.ts @@ -0,0 +1,173 @@ +import { Decimal } from 'decimal.js'; +import { jest } from '@jest/globals'; + +import { NitroliteClient } from '../../src/client.js'; + +const wallet = '0x1111111111111111111111111111111111111111'; +const friend = '0x2222222222222222222222222222222222222222'; + +function makeClient(sessions: any[]) { + const client = Object.create(NitroliteClient.prototype) as any; + client.userAddress = wallet; + client.innerClient = { + getAppSessions: jest.fn().mockResolvedValue({ sessions }), + getConfig: jest.fn(), + }; + client.assetsBySymbol = new Map([ + ['yusd', { decimals: 6 }], + ['yellow', { decimals: 18 }], + ]); + client._lastAppSessionsListError = null; + client._lastAppSessionsListErrorLogged = null; + + return client; +} + +describe('NitroliteClient getAppSessionsList compat mapping', () => { + let infoSpy: jest.SpyInstance; + let warnSpy: jest.SpyInstance; + + beforeEach(() => { + infoSpy = jest.spyOn(console, 'info').mockImplementation(() => {}); + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + infoSpy.mockRestore(); + warnSpy.mockRestore(); + }); + + it('maps the current v1 appDefinition session shape', async () => { + const client = makeClient([ + { + appSessionId: '0xsession', + appDefinition: { + participants: [ + { walletAddress: wallet, signatureWeight: 1 }, + { walletAddress: friend, signatureWeight: 2 }, + ], + quorum: 3, + nonce: 42n, + }, + isClosed: false, + version: 7n, + allocations: [ + { participant: wallet, asset: 'YUSD', amount: new Decimal('1.25') }, + { participant: friend, asset: 'YELLOW', amount: new Decimal('2') }, + ], + sessionData: '{"intent":"purchase"}', + }, + ]); + + const sessions = await client.getAppSessionsList(); + + expect(client.innerClient.getAppSessions).toHaveBeenCalledWith({ + wallet: wallet.toLowerCase(), + }); + expect(sessions).toEqual([ + { + app_session_id: '0xsession', + nonce: 42, + participants: [wallet, friend], + protocol: '', + quorum: 3, + status: 'open', + version: 7, + weights: [1, 2], + allocations: [ + { participant: wallet, asset: 'YUSD', amount: '1.25' }, + { participant: friend, asset: 'YELLOW', amount: '2' }, + ], + sessionData: '{"intent":"purchase"}', + }, + ]); + }); + + it('keeps the legacy flat session shape fallback', async () => { + const client = makeClient([ + { + appSessionId: '0xlegacy', + participants: [ + { walletAddress: wallet, signatureWeight: 50 }, + { walletAddress: friend, signatureWeight: 50 }, + ], + quorum: 100, + nonce: 99n, + isClosed: true, + version: 4n, + allocations: [], + sessionData: '{"legacy":true}', + }, + ]); + + const sessions = await client.getAppSessionsList(wallet, 'any'); + + expect(client.innerClient.getAppSessions).toHaveBeenCalledWith({ + wallet: wallet.toLowerCase(), + }); + expect(sessions).toEqual([ + { + app_session_id: '0xlegacy', + nonce: 99, + participants: [wallet, friend], + protocol: '', + quorum: 100, + status: 'closed', + version: 4, + weights: [50, 50], + allocations: [], + sessionData: '{"legacy":true}', + }, + ]); + }); + + it('maps an empty app session list without requiring legacy fields', async () => { + const client = makeClient([]); + + await expect(client.getAppSessionsList()).resolves.toEqual([]); + expect(client.innerClient.getAppSessions).toHaveBeenCalledWith({ + wallet: wallet.toLowerCase(), + }); + }); + + it('passes through current SDK camelCase getConfig shape', async () => { + const currentConfig = { + nodeAddress: wallet, + nodeVersion: 'test-node', + supportedSigValidators: [0, 1], + blockchains: [ + { + name: 'Sepolia', + id: 11155111n, + channelHubAddress: '0x3333333333333333333333333333333333333333', + lockingContractAddress: '0x4444444444444444444444444444444444444444', + blockStep: 0n, + }, + ], + }; + const client = makeClient([]); + client.innerClient.getConfig.mockResolvedValue(currentConfig); + + await expect(client.getConfig()).resolves.toBe(currentConfig); + }); + + it('documents snake_case getConfig as pass-through, not normalized compat mapping', async () => { + const rawConfig = { + node_address: wallet, + node_version: 'raw-node', + supported_sig_validators: [0, 1], + blockchains: [ + { + name: 'Sepolia', + blockchain_id: '11155111', + channel_hub_address: '0x3333333333333333333333333333333333333333', + locking_contract_address: '0x4444444444444444444444444444444444444444', + }, + ], + }; + const client = makeClient([]); + client.innerClient.getConfig.mockResolvedValue(rawConfig); + + await expect(client.getConfig()).resolves.toBe(rawConfig); + }); +}); diff --git a/sdk/ts-compat/test/unit/config.test.ts b/sdk/ts-compat/test/unit/config.test.ts new file mode 100644 index 000000000..279d5d9c5 --- /dev/null +++ b/sdk/ts-compat/test/unit/config.test.ts @@ -0,0 +1,52 @@ +import * as compat from '../../src/index.js'; +import { blockchainRPCsFromEnv, buildClientOptions } from '../../src/config.js'; + +type SDKConfig = { + blockchainRPCs?: Map; +}; + +describe('compat root barrel config helpers', () => { + const originalRPCsEnv = process.env.NEXT_PUBLIC_BLOCKCHAIN_RPCS; + + afterEach(() => { + if (originalRPCsEnv === undefined) { + delete process.env.NEXT_PUBLIC_BLOCKCHAIN_RPCS; + return; + } + process.env.NEXT_PUBLIC_BLOCKCHAIN_RPCS = originalRPCsEnv; + }); + + it('resolves config helpers from the root barrel', () => { + expect(compat.buildClientOptions).toBe(buildClientOptions); + expect(compat.blockchainRPCsFromEnv).toBe(blockchainRPCsFromEnv); + expect(typeof compat.NitroliteClient).toBe('function'); + }); + + it('returns an empty mapping when NEXT_PUBLIC_BLOCKCHAIN_RPCS is unset', () => { + delete process.env.NEXT_PUBLIC_BLOCKCHAIN_RPCS; + + expect(blockchainRPCsFromEnv()).toEqual({}); + }); + + it('parses multiple RPC mappings and applies them through SDK options', () => { + process.env.NEXT_PUBLIC_BLOCKCHAIN_RPCS = + '11155111:https://rpc.sepolia.example,84532:https://base-sepolia.example'; + + const mappings = blockchainRPCsFromEnv(); + expect(mappings).toEqual({ + 84532: 'https://base-sepolia.example', + 11155111: 'https://rpc.sepolia.example', + }); + + const config: SDKConfig = {}; + const opts = buildClientOptions({ blockchainRPCs: mappings }); + expect(opts).toHaveLength(2); + for (const opt of opts) { + opt(config); + } + + expect(config.blockchainRPCs).toBeInstanceOf(Map); + expect(config.blockchainRPCs?.get(11155111n)).toBe('https://rpc.sepolia.example'); + expect(config.blockchainRPCs?.get(84532n)).toBe('https://base-sepolia.example'); + }); +}); diff --git a/sdk/ts-compat/test/unit/public-api-drift.test.ts b/sdk/ts-compat/test/unit/public-api-drift.test.ts new file mode 100644 index 000000000..820fff7bd --- /dev/null +++ b/sdk/ts-compat/test/unit/public-api-drift.test.ts @@ -0,0 +1,272 @@ +import * as publicApi from '../../src/index.js'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; + +const testDir = path.dirname(fileURLToPath(import.meta.url)); +const packageRoot = path.resolve(testDir, '../..'); + +const FORMAT_FLAGS = + ts.TypeFormatFlags.NoTruncation | + ts.TypeFormatFlags.UseSingleQuotesForStringLiteralType | + ts.TypeFormatFlags.WriteArrayAsGenericType | + ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; + +type PublicApiMember = { + name: string; + kind: string; + signatures?: string[]; + constructors?: string[]; + properties?: string[]; + staticProperties?: string[]; + members?: string[]; + type?: string; + declaration?: string; +}; + +function normalizeText(text: string): string { + return text.replace(/\s+/g, ' ').trim(); +} + +function createPackageProgram() { + const configPath = ts.findConfigFile(packageRoot, ts.sys.fileExists, 'tsconfig.json'); + if (!configPath) throw new Error(`tsconfig.json not found under ${packageRoot}`); + + const configFile = ts.readConfigFile(configPath, ts.sys.readFile); + if (configFile.error) { + throw new Error(ts.flattenDiagnosticMessageText(configFile.error.messageText, '\n')); + } + + const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, packageRoot); + return ts.createProgram(parsed.fileNames, parsed.options); +} + +function declarationKind(declaration: ts.Declaration): string { + if (ts.isClassDeclaration(declaration)) return 'class'; + if (ts.isInterfaceDeclaration(declaration)) return 'interface'; + if (ts.isFunctionDeclaration(declaration)) return 'function'; + if (ts.isEnumDeclaration(declaration)) return 'enum'; + if (ts.isTypeAliasDeclaration(declaration)) return 'type'; + if (ts.isVariableDeclaration(declaration)) return 'const'; + return ts.SyntaxKind[declaration.kind] ?? 'unknown'; +} + +function signaturesForType( + checker: ts.TypeChecker, + type: ts.Type, + declaration: ts.Declaration +): string[] { + return type + .getCallSignatures() + .map((signature) => checker.signatureToString(signature, declaration, FORMAT_FLAGS)) + .sort(); +} + +function isPrivateOrProtected(declaration: ts.Declaration): boolean { + const flags = ts.getCombinedModifierFlags(declaration); + return Boolean(flags & (ts.ModifierFlags.Private | ts.ModifierFlags.Protected)); +} + +function propertiesForType( + checker: ts.TypeChecker, + type: ts.Type, + declaration: ts.Declaration +): string[] { + return checker + .getPropertiesOfType(type) + .flatMap((property) => { + const propertyDeclaration = property.valueDeclaration ?? property.declarations?.[0] ?? declaration; + if (isPrivateOrProtected(propertyDeclaration)) return []; + + const propertyType = checker.getTypeOfSymbolAtLocation(property, propertyDeclaration); + const signatures = signaturesForType(checker, propertyType, propertyDeclaration); + if (signatures.length > 0) { + return [`${property.getName()}: ${signatures.join(' | ')}`]; + } + if ( + (ts.isPropertySignature(propertyDeclaration) || + ts.isPropertyDeclaration(propertyDeclaration)) && + propertyDeclaration.type + ) { + return [`${property.getName()}: ${normalizeText(propertyDeclaration.type.getText())}`]; + } + return [ + `${property.getName()}: ${checker.typeToString(propertyType, propertyDeclaration, FORMAT_FLAGS)}`, + ]; + }) + .sort(); +} + +function enumMembers(declaration: ts.EnumDeclaration): string[] { + return declaration.members.map((member) => { + const initializer = member.initializer ? normalizeText(member.initializer.getText()) : ''; + return `${member.name.getText()} = ${initializer}`; + }); +} + +function serializePublicApi(): PublicApiMember[] { + const program = createPackageProgram(); + const checker = program.getTypeChecker(); + const entrypoint = program.getSourceFile(path.join(packageRoot, 'src/index.ts')); + if (!entrypoint) throw new Error('src/index.ts not found in program'); + + const moduleSymbol = checker.getSymbolAtLocation(entrypoint); + if (!moduleSymbol) throw new Error('src/index.ts module symbol not found'); + + return checker + .getExportsOfModule(moduleSymbol) + .filter((symbol) => symbol.getName() !== '__esModule') + .map((exportedSymbol) => { + const symbol = + exportedSymbol.flags & ts.SymbolFlags.Alias + ? checker.getAliasedSymbol(exportedSymbol) + : exportedSymbol; + const declaration = symbol.getDeclarations()?.[0]; + if (!declaration) { + return { + name: exportedSymbol.getName(), + kind: 'unknown', + }; + } + + const kind = declarationKind(declaration); + const member: PublicApiMember = { + name: exportedSymbol.getName(), + kind, + }; + + if (ts.isClassDeclaration(declaration)) { + const staticType = checker.getTypeOfSymbolAtLocation(symbol, declaration); + const instanceType = checker.getDeclaredTypeOfSymbol(symbol); + member.constructors = staticType + .getConstructSignatures() + .map((signature) => checker.signatureToString(signature, declaration, FORMAT_FLAGS)) + .sort(); + member.properties = propertiesForType(checker, instanceType, declaration); + member.staticProperties = propertiesForType(checker, staticType, declaration).filter( + (property) => !['length', 'name', 'prototype'].some((skip) => property.startsWith(`${skip}:`)) + ); + } else if (ts.isInterfaceDeclaration(declaration)) { + const type = checker.getDeclaredTypeOfSymbol(symbol); + member.properties = propertiesForType(checker, type, declaration); + member.signatures = signaturesForType(checker, type, declaration); + } else if (ts.isFunctionDeclaration(declaration)) { + member.signatures = signaturesForType( + checker, + checker.getTypeOfSymbolAtLocation(symbol, declaration), + declaration + ); + } else if (ts.isEnumDeclaration(declaration)) { + member.members = enumMembers(declaration); + } else if (ts.isTypeAliasDeclaration(declaration)) { + member.declaration = normalizeText(declaration.type.getText()); + member.type = checker.typeToString( + checker.getTypeFromTypeNode(declaration.type), + declaration, + FORMAT_FLAGS + ); + } else if (ts.isVariableDeclaration(declaration)) { + member.type = checker.typeToString( + checker.getTypeOfSymbolAtLocation(symbol, declaration), + declaration, + FORMAT_FLAGS + ); + } + + return member; + }) + .sort((a, b) => a.name.localeCompare(b.name)); +} + +describe('compat public runtime API drift guard', () => { + it('keeps root runtime exports intentional', () => { + expect(Object.keys(publicApi).sort()).toMatchSnapshot(); + }); + + it('keeps root TypeScript public API signatures intentional', () => { + expect(serializePublicApi()).toMatchSnapshot(); + }); + + it('keeps session-key compat helpers and client methods public', () => { + expect(Object.keys(publicApi)).toEqual( + expect.arrayContaining(['toSessionKeyQuorumSignature', 'NitroliteClient']) + ); + + const api = serializePublicApi(); + const client = api.find((member) => member.name === 'NitroliteClient'); + expect(client?.properties).toEqual( + expect.arrayContaining([ + expect.stringContaining('signChannelSessionKeyState:'), + expect.stringContaining('signChannelSessionKeyOwnership:'), + expect.stringContaining('submitChannelSessionKeyState:'), + expect.stringContaining('getLastChannelKeyStates:'), + expect.stringContaining('signSessionKeyState:'), + expect.stringContaining('signAppSessionKeyOwnership:'), + expect.stringContaining('submitSessionKeyState:'), + expect.stringContaining('getLastAppKeyStates:'), + expect.stringContaining('getLastKeyStates:'), + ]) + ); + + const helper = api.find((member) => member.name === 'toSessionKeyQuorumSignature'); + expect(helper?.signatures?.[0]).toContain('signature: Hex | string'); + }); + + it('keeps NitroliteClient exported', () => { + expect(Object.keys(publicApi)).toContain('NitroliteClient'); + }); + + it('proves adversarial public signature changes are observable', () => { + const api = serializePublicApi(); + const client = api.find((member) => member.name === 'NitroliteClient'); + expect(client?.properties?.some((property) => property.includes('ping:'))).toBe(true); + + const mutated = api.map((member) => + member.name === 'NitroliteClient' + ? { + ...member, + properties: member.properties?.filter((property) => !property.includes('ping:')), + } + : member + ); + const mutatedClient = mutated.find((member) => member.name === 'NitroliteClient'); + + expect(mutatedClient?.properties?.some((property) => property.includes('ping:'))).toBe(false); + }); + + it('proves adversarial type-only export removal is observable', () => { + const api = serializePublicApi(); + expect(api.some((member) => member.name === 'NitroliteClientConfig' && member.kind === 'interface')).toBe(true); + + const mutated = api.filter((member) => member.name !== 'NitroliteClientConfig'); + expect(mutated.some((member) => member.name === 'NitroliteClientConfig')).toBe(false); + }); + + it('proves adversarial function parameter changes are observable', () => { + const api = serializePublicApi(); + const builder = api.find((member) => member.name === 'buildClientOptions'); + const original = builder?.signatures?.[0] ?? ''; + expect(original).toContain('config: CompatClientConfig'); + + const mutated = original.replace('config: CompatClientConfig', 'config: unknown'); + expect(mutated).not.toEqual(original); + }); + + it('proves adversarial enum value changes are observable', () => { + const api = serializePublicApi(); + const method = api.find((member) => member.name === 'RPCMethod'); + const original = method?.members?.join('|') ?? ''; + expect(original).toContain('Ping'); + + const mutated = original.replace('Ping', 'PingChanged'); + expect(mutated).not.toEqual(original); + }); + + it('proves adversarial public export additions are observable', () => { + const api = serializePublicApi(); + expect(api.some((member) => member.name === '__FakeExport')).toBe(false); + + const mutated = [...api, { name: '__FakeExport', kind: 'function' }]; + expect(mutated.some((member) => member.name === '__FakeExport')).toBe(true); + }); +}); diff --git a/sdk/ts-compat/test/unit/rpc-wire-shape.test.ts b/sdk/ts-compat/test/unit/rpc-wire-shape.test.ts new file mode 100644 index 000000000..aaee33bac --- /dev/null +++ b/sdk/ts-compat/test/unit/rpc-wire-shape.test.ts @@ -0,0 +1,269 @@ +import { jest } from '@jest/globals'; + +import { + RPCAppStateIntent, + createAppSessionMessage, + createCloseAppSessionMessage, + createCloseChannelMessage, + createGetAppDefinitionMessage, + createGetAppSessionsMessage, + createGetChannelsMessage, + createGetLedgerBalancesMessage, + createPingMessage, + createSubmitAppStateMessage, + createTransferMessage, + parseCreateAppSessionResponse, + parseGetAppDefinitionResponse, + parseGetAppSessionsResponse, +} from '../../src/index.js'; + +const signer = jest.fn(async () => '0xsigned'); +const participant = '0x00000000000000000000000000000000000000a1'; +const otherParticipant = '0x00000000000000000000000000000000000000b2'; + +type CompatRequest = { + req: [number, string, Record, number]; + sig: string; +}; + +function parseCompatRequest(raw: string): CompatRequest { + return JSON.parse(raw) as CompatRequest; +} + +describe('compat RPC helper wire shapes', () => { + beforeEach(() => { + signer.mockClear(); + }); + + it('creates a real v1 ping payload inside the legacy req/sig envelope', async () => { + const raw = await createPingMessage(signer, 7, 1234); + const parsed = parseCompatRequest(raw); + + expect(parsed).toEqual({ + req: [7, 'node.v1.ping', {}, 1234], + sig: '', + }); + expect(signer).not.toHaveBeenCalled(); + }); + + it('requires participant for getChannels and emits the v1 method name', async () => { + const raw = await createGetChannelsMessage(signer, participant, 'open', 11, 999); + const parsed = parseCompatRequest(raw); + + expect(parsed.req).toEqual([ + 11, + 'channels.v1.get_channels', + { wallet: participant, status: 'open' }, + 999, + ]); + expect(parsed.sig).toBe(''); + expect(signer).not.toHaveBeenCalled(); + + await expect(createGetChannelsMessage(signer, undefined, 'open')).rejects.toThrow( + 'createGetChannelsMessage requires participant', + ); + expect(signer).not.toHaveBeenCalled(); + }); + + it('requires wallet/account for getLedgerBalances and signs the request', async () => { + const raw = await createGetLedgerBalancesMessage(signer, participant, 12, 555); + const parsed = parseCompatRequest(raw); + + expect(parsed.req).toEqual([ + 12, + 'user.v1.get_balances', + { wallet: participant }, + 555, + ]); + expect(parsed.sig).toBe('0xsigned'); + expect(signer).toHaveBeenCalledTimes(1); + + await expect(createGetLedgerBalancesMessage(signer)).rejects.toThrow( + 'createGetLedgerBalancesMessage requires accountId', + ); + expect(signer).toHaveBeenCalledTimes(1); + }); + + it('maps app-session query helpers to live v1 methods', async () => { + const sessionsRaw = await createGetAppSessionsMessage(signer, participant, 'open', 13, 777); + const sessionsReq = parseCompatRequest(sessionsRaw); + expect(sessionsReq).toEqual({ + req: [13, 'app_sessions.v1.get_app_sessions', { participant, status: 'open' }, 777], + sig: '', + }); + + const definitionRaw = await createGetAppDefinitionMessage(signer, 'session-1', 14, 778); + const definitionReq = parseCompatRequest(definitionRaw); + expect(definitionReq).toEqual({ + req: [14, 'app_sessions.v1.get_app_definition', { app_session_id: 'session-1' }, 778], + sig: '', + }); + expect(signer).not.toHaveBeenCalled(); + }); + + it('creates a signed v1 create_app_session request and encodes legacy allocations into session_data', async () => { + const raw = await createAppSessionMessage( + signer, + { + definition: { + application: 'chess', + protocol: '' as never, + participants: [participant, otherParticipant] as [`0x${string}`, `0x${string}`], + weights: [1, 2], + quorum: 2, + nonce: 42, + }, + allocations: [ + { participant, asset: 'yusd', amount: '0.25' }, + { participant: otherParticipant, asset: 'yusd', amount: '0.75' }, + ], + quorum_sigs: ['0xsig1', '0xsig2'], + }, + 15, + 779, + ); + + const parsed = parseCompatRequest(raw); + expect(parsed.sig).toBe('0xsigned'); + expect(signer).toHaveBeenCalledTimes(1); + expect(parsed.req[0]).toBe(15); + expect(parsed.req[1]).toBe('app_sessions.v1.create_app_session'); + expect(parsed.req[2]).toEqual({ + definition: { + application_id: 'chess', + participants: [ + { wallet_address: participant, signature_weight: 1 }, + { wallet_address: otherParticipant, signature_weight: 2 }, + ], + quorum: 2, + nonce: '42', + }, + quorum_sigs: ['0xsig1', '0xsig2'], + session_data: JSON.stringify({ + allocations: [ + { participant, asset: 'yusd', amount: '0.25' }, + { participant: otherParticipant, asset: 'yusd', amount: '0.75' }, + ], + }), + }); + expect(parsed.req[3]).toBe(779); + }); + + it('requires explicit version for submit/close app-state mappings and emits submit_app_state', async () => { + const submitRaw = await createSubmitAppStateMessage( + signer, + { + app_session_id: 'session-1' as `0x${string}`, + intent: RPCAppStateIntent.Operate, + version: 7, + allocations: [{ participant, asset: 'yusd', amount: '0.01' }], + session_data: '{"move":"e4"}', + quorum_sigs: ['0xsig'], + }, + 16, + 780, + ); + const submitReq = parseCompatRequest(submitRaw); + expect(submitReq.sig).toBe('0xsigned'); + expect(signer).toHaveBeenCalledTimes(1); + expect(submitReq.req).toEqual([ + 16, + 'app_sessions.v1.submit_app_state', + { + app_state_update: { + app_session_id: 'session-1', + intent: 0, + version: '7', + allocations: [{ participant, asset: 'yusd', amount: '0.01' }], + session_data: '{"move":"e4"}', + }, + quorum_sigs: ['0xsig'], + }, + 780, + ]); + + const closeRaw = await createCloseAppSessionMessage( + signer, + { + app_session_id: 'session-2', + allocations: [{ participant, asset: 'yusd', amount: '1.5' }], + version: 9, + quorum_sigs: ['0xclose'], + }, + 17, + 781, + ); + const closeReq = parseCompatRequest(closeRaw); + expect(closeReq.sig).toBe('0xsigned'); + expect(signer).toHaveBeenCalledTimes(2); + expect(closeReq.req).toEqual([ + 17, + 'app_sessions.v1.submit_app_state', + { + app_state_update: { + app_session_id: 'session-2', + intent: 3, + version: '9', + allocations: [{ participant, asset: 'yusd', amount: '1.5' }], + session_data: '', + }, + quorum_sigs: ['0xclose'], + }, + 781, + ]); + + await expect( + createSubmitAppStateMessage(signer, { + app_session_id: 'session-3' as `0x${string}`, + allocations: [{ participant, asset: 'yusd', amount: '0.01' }], + }), + ).rejects.toThrow('createSubmitAppStateMessage requires params.version'); + expect(signer).toHaveBeenCalledTimes(2); + + await expect( + createCloseAppSessionMessage(signer, { + app_session_id: 'session-4', + allocations: [{ participant, asset: 'yusd', amount: '0.01' }], + quorum_sigs: [], + }), + ).rejects.toThrow('createCloseAppSessionMessage requires params.version'); + expect(signer).toHaveBeenCalledTimes(2); + }); + + it('fails fast for legacy workflow helpers that do not map to a single v1 RPC', async () => { + await expect(createTransferMessage(signer, { destination: participant })).rejects.toThrow( + 'NitroliteClient.transfer(destination, allocations)', + ); + await expect(createCloseChannelMessage(signer, 'channel-1', participant)).rejects.toThrow( + 'NitroliteClient.closeChannel(...)', + ); + }); + + it('normalizes snake_case live responses into legacy parse helper shapes', () => { + expect( + parseGetAppSessionsResponse( + JSON.stringify({ res: [1, 'app_sessions.v1.get_app_sessions', { app_sessions: [{ app_session_id: 's1' }] }, 1234] }), + ), + ).toEqual({ params: { appSessions: [{ app_session_id: 's1' }] } }); + + expect( + parseCreateAppSessionResponse( + JSON.stringify({ res: [1, 'app_sessions.v1.create_app_session', { app_session_id: 's1', version: '1', status: 'open' }, 1234] }), + ), + ).toEqual({ params: { appSessionId: 's1', version: '1', status: 'open' } }); + + expect( + parseGetAppDefinitionResponse( + JSON.stringify({ res: [1, 'app_sessions.v1.get_app_definition', { definition: { application_id: 'app-1' } }, 1234] }), + ), + ).toEqual({ params: { application_id: 'app-1' } }); + }); + + it('accepts bare legacy response arrays using the same tuple layout', () => { + expect( + parseGetAppSessionsResponse( + JSON.stringify([1, 'app_sessions.v1.get_app_sessions', { app_sessions: [{ app_session_id: 's1' }] }, 1234]), + ), + ).toEqual({ params: { appSessions: [{ app_session_id: 's1' }] } }); + }); +}); diff --git a/sdk/ts-compat/tsconfig.json b/sdk/ts-compat/tsconfig.json index 61e3319a8..bccc14888 100644 --- a/sdk/ts-compat/tsconfig.json +++ b/sdk/ts-compat/tsconfig.json @@ -1,17 +1,18 @@ { "compilerOptions": { "target": "es2020", - "module": "ESNext", + "module": "Node16", "declaration": true, "declarationMap": true, "sourceMap": true, "outDir": "./dist", + "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "moduleResolution": "bundler", + "moduleResolution": "Node16", "isolatedModules": true, "types": ["node"] }, diff --git a/sdk/ts/CLAUDE.md b/sdk/ts/CLAUDE.md new file mode 100644 index 000000000..cf5ffa0b2 --- /dev/null +++ b/sdk/ts/CLAUDE.md @@ -0,0 +1,67 @@ +# TypeScript SDK (`@yellow-org/sdk`) + +Official TypeScript SDK for building web and mobile applications on Nitrolite state channels. + +## Quick Reference + +| Command | What it does | +|---------|-------------| +| `npm test` | Run unit tests (Jest) | +| `npm run build` | Run tests **then** compile (`npm run test && tsc`) | +| `npm run typecheck` | Type check only (no emit) | +| `npm run lint` | ESLint | +| `npm run test:integration` | Integration tests (separate Jest config) | +| `npm run test:all` | Unit + integration tests | + +**Important:** `npm run build` runs the full test suite before compiling. If you just want to compile, run `npx tsc` directly. + +## Package Details + +- **Name:** `@yellow-org/sdk` +- **Version:** 1.2.0 +- **Module:** ESM-only (`"type": "module"`) +- **Node:** >=20.0.0 +- **Entry:** `dist/index.js` / `dist/index.d.ts` + +## TypeScript Configuration + +- Target: `es2020` +- Module: `ESNext` +- Module Resolution: `bundler` +- Strict mode: enabled +- Declaration files: generated + +## Code Style + +- Prettier: 120 char width, 4-space indent, single quotes, semicolons +- ESLint configured via `@typescript-eslint` + +## Source Layout + +| Path | Purpose | +|------|---------| +| `src/index.ts` | Barrel export — all public API goes here | +| `src/client.ts` | Main SDK client (~1100 lines) | +| `src/signers.ts` | Signer implementations (ECDSA, session key, channel) | +| `src/config.ts` | Client configuration with functional options | +| `src/core/` | State machine, types, events, utilities | +| `src/rpc/` | RPC client, message types, API methods | +| `src/blockchain/` | On-chain interactions (deposits, withdrawals) | +| `src/app/` | App session logic and packing | +| `src/utils.ts` | Shared utility functions | +| `test/unit/` | Unit tests | + +## Test Setup + +- Framework: Jest with ts-jest +- Config: `jest.config.cjs` (unit), `jest.integration.config.js` (integration) +- Path alias: `@/` maps to `src/` in test configs +- Pattern: data-driven tests with `.forEach()`, manual mocks (not jest.mock) +- Naming: `*.test.ts` (not `.spec.ts`) + +## Key Dependencies + +- `viem` — Ethereum interactions (NOT ethers.js in production code; ethers is dev-only) +- `decimal.js` — Precise decimal arithmetic for token amounts +- `zod` — Runtime validation +- `abitype` — ABI type utilities diff --git a/sdk/ts/README.md b/sdk/ts/README.md index fa42f0edf..8ffc2310d 100644 --- a/sdk/ts/README.md +++ b/sdk/ts/README.md @@ -3,11 +3,13 @@ [![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/layer-3/nitrolite/blob/main/LICENSE) -TypeScript SDK for Clearnode payment channels providing both high-level and low-level operations in a unified client: +> The off-chain broker was renamed from "Clearnode" to "Nitronode" in v1.3.0. The default WebSocket sandbox URL is now `wss://nitronode-sandbox.yellow.org/v1/ws`. See [`MIGRATION-NITRONODE.md`](../../MIGRATION-NITRONODE.md). + +TypeScript SDK for Nitronode 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 - **Blockchain Settlement**: `checkpoint()` - the single entry point for all on-chain transactions - **Low-Level Operations**: Direct RPC access for custom flows and advanced use cases -- **Full Feature Parity**: 100% compatibility with Go SDK functionality +- **Go-Aligned Surface**: core channel, blockchain, and app-session flows follow the Go SDK patterns without claiming complete parity for every helper > 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. @@ -30,6 +32,11 @@ client.approveToken(chainId, asset, amount) // Approve token spending client.checkTokenAllowance(chainId, token, owner) // Check token allowance ``` +### On-Chain Queries +```typescript +client.getOnChainBalance(chainId, asset, wallet) // Query on-chain token or native balance +``` + ### Node Information ```typescript client.ping() // Health check @@ -71,16 +78,18 @@ client.rebalanceAppSessions(signedUpdates) // Atomic rebala ### App Session Keys ```typescript -client.signSessionKeyState(state) // Sign app session key state -client.submitSessionKeyState(state) // Register/update app session key -client.getLastKeyStates(userAddress, sessionKey?) // Get active app session key states +client.signSessionKeyState(state) // Wallet user_sig over app session key state +client.signAppSessionKeyOwnership(state, sessionKeySigner) // Session-key holder's session_key_sig +client.submitSessionKeyState(state) // Register/update app session key (both sigs required) +client.getLastAppKeyStates(userAddress, sessionKey?, options?) // Get app session key states (active-only by default; pass { includeInactive: true } to include expired) ``` ### Channel Session Keys ```typescript -client.signChannelSessionKeyState(state) // Sign channel session key state -client.submitChannelSessionKeyState(state) // Register/update channel session key -client.getLastChannelKeyStates(userAddress, sessionKey?) // Get active channel session key states +client.signChannelSessionKeyState(state) // Wallet user_sig over channel session key state +client.signChannelSessionKeyOwnership(state, sessionKeySigner) // Session-key holder's session_key_sig +client.submitChannelSessionKeyState(state) // Register/update channel session key (both sigs required) +client.getLastChannelKeyStates(userAddress, sessionKey?, options?) // Get channel session key states (active-only by default; pass { includeInactive: true } to include expired) ``` ### Shared Utilities @@ -118,7 +127,7 @@ async function main() { // Create unified client const client = await Client.create( - 'wss://clearnode.example.com/ws', + 'wss://nitronode.example.com/ws', stateSigner, txSigner, withBlockchainRPC(80002n, 'https://polygon-amoy.alchemy.com/v2/KEY') @@ -485,58 +494,65 @@ const sessionKeySigner = new AppSessionKeySignerV1(sessionKeyMsgSigner); ### App Session Keys +Registration requires two signatures: the wallet's `user_sig` (authorizing the +delegation) and the session-key holder's `session_key_sig` (proving possession of the key +being registered). The node rejects submits that lack a valid `session_key_sig`. + ```typescript -// Sign and submit an app session key state -const sig = await client.signSessionKeyState({ +// sessionKeyHolder is an EthereumMsgSigner whose address equals state.session_key. +// Use a raw message signer (not a wrapped StateSigner) — the node expects a +// raw 65-byte EIP-191 signature for session_key_sig. +const sessionKeyHolder = new EthereumMsgSigner(sessionKeyPrivateKey); +const state = { user_address: '0x1234...', session_key: '0xabcd...', version: '1', application_ids: ['app1'], app_session_ids: [], expires_at: String(Math.floor(Date.now() / 1000) + 86400), - user_sig: '0x', -}); + user_sig: '', + session_key_sig: '', +}; +state.user_sig = await client.signSessionKeyState(state); +state.session_key_sig = await client.signAppSessionKeyOwnership(state, sessionKeyHolder); -await client.submitSessionKeyState({ - user_address: '0x1234...', - session_key: '0xabcd...', - version: '1', - application_ids: ['app1'], - app_session_ids: [], - expires_at: String(Math.floor(Date.now() / 1000) + 86400), - user_sig: sig, -}); +await client.submitSessionKeyState(state); + +// Query app session key states (active-only by default) +const states = await client.getLastAppKeyStates('0x1234...'); +const filtered = await client.getLastAppKeyStates('0x1234...', '0xSessionKey...'); -// Query active app session key states -const states = await client.getLastKeyStates('0x1234...'); -const filtered = await client.getLastKeyStates('0x1234...', '0xSessionKey...'); +// Include expired/revoked latest states (e.g. for rotation flows that need the prior version) +const all = await client.getLastAppKeyStates('0x1234...', '0xSessionKey...', { includeInactive: true }); ``` ### Channel Session Keys ```typescript -// Sign and submit a channel session key state -const sig = await client.signChannelSessionKeyState({ +// sessionKeyHolder is an EthereumMsgSigner whose address equals state.session_key. +// Use a raw message signer (not a wrapped StateSigner) — the node expects a +// raw 65-byte EIP-191 signature for session_key_sig. +const sessionKeyHolder = new EthereumMsgSigner(sessionKeyPrivateKey); +const state = { user_address: '0x1234...', session_key: '0xabcd...', version: '1', assets: ['usdc'], expires_at: String(Math.floor(Date.now() / 1000) + 86400), - user_sig: '0x', -}); + user_sig: '', + session_key_sig: '', +}; +state.user_sig = await client.signChannelSessionKeyState(state); +state.session_key_sig = await client.signChannelSessionKeyOwnership(state, sessionKeyHolder); -await client.submitChannelSessionKeyState({ - user_address: '0x1234...', - session_key: '0xabcd...', - version: '1', - assets: ['usdc'], - expires_at: String(Math.floor(Date.now() / 1000) + 86400), - user_sig: sig, -}); +await client.submitChannelSessionKeyState(state); -// Query active channel session key states +// Query channel session key states (active-only by default) const states = await client.getLastChannelKeyStates('0x1234...'); const filtered = await client.getLastChannelKeyStates('0x1234...', '0xSessionKey...'); + +// Include expired/revoked latest states (e.g. for rotation flows that need the prior version) +const all = await client.getLastChannelKeyStates('0x1234...', '0xSessionKey...', { includeInactive: true }); ``` ## Key Concepts @@ -683,7 +699,7 @@ async function basicExample() { const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); const client = await Client.create( - 'wss://clearnode.example.com/ws', + 'wss://nitronode.example.com/ws', stateSigner, txSigner, withBlockchainRPC(80002n, process.env.RPC_URL!) @@ -732,7 +748,7 @@ async function multiChainExample() { const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); const client = await Client.create( - 'wss://clearnode.example.com/ws', + 'wss://nitronode.example.com/ws', stateSigner, txSigner, withBlockchainRPC(80002n, process.env.POLYGON_RPC!), // Polygon Amoy @@ -770,7 +786,7 @@ import { Client, createSigners } from '@yellow-org/sdk'; async function queryTransactions() { const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); const client = await Client.create( - 'wss://clearnode.example.com/ws', + 'wss://nitronode.example.com/ws', stateSigner, txSigner ); @@ -816,7 +832,7 @@ import Decimal from 'decimal.js'; async function appSessionExample() { const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); const client = await Client.create( - 'wss://clearnode.example.com/ws', + 'wss://nitronode.example.com/ws', stateSigner, txSigner, withBlockchainRPC(80002n, process.env.RPC_URL!) @@ -897,7 +913,7 @@ async function monitorConnection() { const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); const client = await Client.create( - 'wss://clearnode.example.com/ws', + 'wss://nitronode.example.com/ws', stateSigner, txSigner, withPingInterval(3000), @@ -1068,7 +1084,7 @@ For understanding how operations work under the hood: - **Node.js**: 20.0.0 or later - **TypeScript**: 5.3.0 or later (for development) -- **Running Clearnode instance** or access to public node +- **Running Nitronode instance** or access to public node - **Blockchain RPC endpoint** (for on-chain operations via `checkpoint()`) ## License diff --git a/sdk/ts/eslint.config.cjs b/sdk/ts/eslint.config.cjs new file mode 100644 index 000000000..bb8eefb33 --- /dev/null +++ b/sdk/ts/eslint.config.cjs @@ -0,0 +1,27 @@ +const tseslint = require('@typescript-eslint/eslint-plugin'); +const tsParser = require('@typescript-eslint/parser'); + +module.exports = [ + { + ignores: ['dist/**', 'node_modules/**'], + }, + { + files: ['src/**/*.ts', 'test/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + ...tseslint.configs.recommended.rules, + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'always' }], + }, + }, +]; diff --git a/sdk/ts/examples/app_sessions/README.md b/sdk/ts/examples/app_sessions/README.md index 819f1f751..ad8e31efa 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://clearnode-sandbox.yellow.org/v1/ws`) +- Access to a Nitrolite node (default: `wss://nitronode-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 55cbbc60d..0bbbaf895 100644 --- a/sdk/ts/examples/app_sessions/lifecycle.ts +++ b/sdk/ts/examples/app_sessions/lifecycle.ts @@ -1,23 +1,54 @@ /** * 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) + * Requirements to run this example: + * + * 1. A reachable nitronode WebSocket endpoint (set via wsURL below). + * The default points at the public sandbox. + * + * 2. Three EVM wallets with hex private keys (replace the placeholders below). + * Wallet 3 may be a fresh key — it only receives funds via redistribution. + * + * 3. Minimum off-chain (channel) balances on the node: + * - Wallet 1: 0.0001 YUSD (deposited into Session 1) + * - Wallet 2: 0.00015 YELLOW (deposited into Session 2) + * - Wallet 3: none required (receives funds via redistribution) + * + * An open channel is NOT a hard prerequisite. If a wallet already has + * funds on the node but no acknowledged channel for the asset yet, the + * example calls acknowledge() first to open one. Wallet 3 also needs no + * pre-existing channel; the withdraw step will open/credit its ledger + * automatically. + * + * 4. App registry: if the node was started with the app registry disabled + * (apps.v1 group disabled), the registration step is skipped at runtime + * and app sessions are created against unregistered app IDs. No action + * is required from the operator — the example detects this via a probe + * call to getApps(). * * This example demonstrates: - * 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) + * 1. Register apps in the app registry (skipped if apps.v1 group is disabled) + * 2. Create first app session for wallet 1 + * 3. Deposit YUSD into first app session by wallet 1 + * (auto-opens wallet 1's YUSD channel via acknowledge() if missing) + * 4. Create second app session for wallet 2 with wallet 3 as a participant + * 5. Deposit YELLOW into second app session by wallet 2 + * (auto-opens wallet 2's YELLOW channel via acknowledge() if missing) + * 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). + * Skipped entirely when the app registry is disabled. */ +// Node <22 does not expose a stable global WebSocket. The SDK dialer uses +// `new WebSocket(url)` directly, so we polyfill the global from the `ws` +// package before any client is constructed. +import WebSocket from 'ws'; +if (typeof (globalThis as { WebSocket?: unknown }).WebSocket === 'undefined') { + (globalThis as { WebSocket: unknown }).WebSocket = WebSocket; +} + import Decimal from 'decimal.js'; import { Hex } from 'viem'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; @@ -35,10 +66,41 @@ import { AppSessionKeyStateV1, } from '../../src/app/types'; import { packCreateAppSessionRequestV1, packAppStateUpdateV1, packAppSessionKeyStateV1 } from '../../src/app/packing'; +import { isFinal } from '../../src/core/state'; + +// appRegistryDisabledMsg is the error fragment returned by the node when the +// apps.v1 RPC group is disabled by configuration. The example uses this to +// decide whether to skip the registration step. +const APP_REGISTRY_DISABLED_MSG = 'apps.v1 group is disabled'; + +/** + * ensureChannelOpen guarantees that the given wallet has an acknowledged + * channel open for asset. If the node holds no state for the wallet/asset + * pair, or the latest state is still awaiting the user's signature (or has + * been finalized), acknowledge() is invoked to create or progress the channel. + * Already-acknowledged channels are left untouched. + */ +async function ensureChannelOpen(label: string, client: Client, asset: string): Promise { + const wallet = client.getUserAddress(); + const state = await client.getLatestState(wallet, asset, false); + + const hasOpenChannel = + state !== null && + state.homeChannelId !== undefined && + !isFinal(state) && + !!state.userSig; + if (hasOpenChannel) { + console.log(`✓ ${label} already has an open ${asset} channel`); + return; + } + + await client.acknowledge(asset); + console.log(`✓ ${label} acknowledged ${asset} channel`); +} async function main() { // Replace with a real deployment url - const wsURL = 'wss://clearnode-sandbox.yellow.org/v1/ws'; + const wsURL = 'wss://nitronode-sandbox.yellow.org/v1/ws'; // --- 0. Setup Wallets --- // Replace these strings with your actual hex private keys @@ -85,6 +147,15 @@ async function main() { wallet3Signers.txSigner ); + // --- Ensure Required Channels Are Open --- + // App session deposits require an acknowledged channel for the asset. + // If the wallet has funds on the node but no channel yet, acknowledge() + // opens it on the fly so the example only assumes a minimum balance. + console.log('=== Ensuring Channels Are Open ==='); + await ensureChannelOpen('Wallet 1', wallet1Client, 'yusd'); + await ensureChannelOpen('Wallet 2', wallet2Client, 'yellow'); + console.log(); + // --- 1. Register Apps --- console.log('=== Step 1: Registering Apps ==='); @@ -92,11 +163,30 @@ async function main() { const app1ID = `test-app-${suffix}`; const app2ID = `multi-party-app-${suffix}`; - await wallet1Client.registerApp(app1ID, '{}', true); - console.log(`✓ Registered app: ${app1ID}`); + // Probe the apps.v1 group via getApps. If the node has the app registry + // disabled, the probe throws an error containing APP_REGISTRY_DISABLED_MSG + // and we skip registration entirely — app sessions can still be created + // against unregistered IDs in that mode. + let appRegistryEnabled = true; + try { + await wallet1Client.getApps(); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + if (msg.includes(APP_REGISTRY_DISABLED_MSG)) { + appRegistryEnabled = false; + console.log('ℹ App registry is disabled on the node — skipping app registration'); + } else { + throw err; + } + } + + if (appRegistryEnabled) { + await wallet1Client.registerApp(app1ID, '{}', true); + console.log(`✓ Registered app: ${app1ID}`); - await wallet1Client.registerApp(app2ID, '{}', false); - console.log(`✓ Registered app: ${app2ID} (owner approval required)\n`); + 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) ==='); @@ -118,8 +208,8 @@ async function main() { ); console.log(`✓ Created App Session 1: ${session1ID}\n`); - // --- 3. Deposit USDC into Session 1 --- - console.log('=== Step 3: Depositing USDC into Session 1 ==='); + // --- 3. Deposit YUSD into Session 1 --- + console.log('=== Step 3: Depositing YUSD into Session 1 ==='); const session1DepositAmount = new Decimal(0.0001); const session1DepositUpdate: AppStateUpdateV1 = { @@ -127,7 +217,7 @@ async function main() { intent: AppStateUpdateIntent.Deposit, version: 2n, allocations: [ - { participant: wallet1Address, asset: 'usdc', amount: session1DepositAmount }, + { participant: wallet1Address, asset: 'yusd', amount: session1DepositAmount }, ], sessionData: '{}', }; @@ -139,10 +229,10 @@ async function main() { await wallet1Client.submitAppSessionDeposit( session1DepositUpdate, [appSession1DepositSig], - 'usdc', + 'yusd', session1DepositAmount ); - console.log(`✓ Deposited ${session1DepositAmount} USDC into Session 1\n`); + console.log(`✓ Deposited ${session1DepositAmount} YUSD into Session 1\n`); } catch (err) { console.log(`⚠ Deposit warning: ${err}`); } @@ -167,12 +257,18 @@ async function main() { app_session_ids: [], expires_at: String(expiresAt), user_sig: '', + session_key_sig: '', }; const packedAppSessionKey3State = packAppSessionKeyStateV1(appSessionKey3State); + + // Wallet's user_sig authorizes the delegation. const wallet3MsgSigner = new EthereumMsgSigner(wallet3PrivateKey); - const appSessionKey3StateSig = await wallet3MsgSigner.signMessage(packedAppSessionKey3State); - appSessionKey3State.user_sig = appSessionKey3StateSig; + appSessionKey3State.user_sig = await wallet3MsgSigner.signMessage(packedAppSessionKey3State); + + // Session-key holder's session_key_sig proves possession of the key being registered. + // Both signatures are required at submit time. + appSessionKey3State.session_key_sig = await sessionKey3MsgSigner.signMessage(packedAppSessionKey3State); await wallet3Client.submitSessionKeyState(appSessionKey3State); @@ -203,8 +299,8 @@ async function main() { ); console.log(`✓ Created App Session 2: ${session2ID}\n`); - // --- 5. Deposit WETH into Session 2 by Wallet 2 --- - console.log('=== Step 5: Depositing WETH into Session 2 ==='); + // --- 5. Deposit YELLOW into Session 2 by Wallet 2 --- + console.log('=== Step 5: Depositing YELLOW into Session 2 ==='); const session2DepositAmount = new Decimal(0.00015); const session2DepositUpdate: AppStateUpdateV1 = { @@ -212,7 +308,7 @@ async function main() { intent: AppStateUpdateIntent.Deposit, version: 2n, allocations: [ - { participant: wallet2Address, asset: 'weth', amount: session2DepositAmount }, + { participant: wallet2Address, asset: 'yellow', amount: session2DepositAmount }, ], sessionData: '{}', }; @@ -224,10 +320,10 @@ async function main() { const nodeSig = await wallet2Client.submitAppSessionDeposit( session2DepositUpdate, [appSession2DepositSig, appSession3DepositSig], - 'weth', + 'yellow', session2DepositAmount ); - console.log(`✓ Deposited ${session2DepositAmount} WETH into Session 2 (Node Sig: ${nodeSig})\n`); + console.log(`✓ Deposited ${session2DepositAmount} YELLOW into Session 2 (Node Sig: ${nodeSig})\n`); // Check Session 2 state before redistribution const { sessions: session2InfoBeforeRedist } = await wallet2Client.getAppSessions({ @@ -247,8 +343,8 @@ async function main() { intent: AppStateUpdateIntent.Operate, version: 3n, allocations: [ - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.0001) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.00005) }, + { participant: wallet2Address, asset: 'yellow', amount: new Decimal(0.0001) }, + { participant: wallet3Address, asset: 'yellow', amount: new Decimal(0.00005) }, ], sessionData: '{}', }; @@ -267,7 +363,7 @@ async function main() { appSession2RedistributeSig, appSession3RedistributeSig, ]); - console.log('✓ Redistributed WETH: Wallet 2 (0.0001) -> Wallet 3 (0.00005)\n'); + console.log('✓ Redistributed YELLOW: Wallet 2 (0.0001) -> Wallet 3 (0.00005)\n'); } catch (err) { console.error(`Redistribution failed: ${err}`); throw err; @@ -285,8 +381,8 @@ async function main() { intent: AppStateUpdateIntent.Withdraw, version: 4n, allocations: [ - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.00005) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.00001) }, + { participant: wallet2Address, asset: 'yellow', amount: new Decimal(0.00005) }, + { participant: wallet3Address, asset: 'yellow', amount: new Decimal(0.00001) }, ], sessionData: '{}', }; @@ -304,7 +400,7 @@ async function main() { appSession2WithdrawSig, appSession3WithdrawSig, ]); - console.log('✓ Wallet 3 successfully withdrew WETH back to channel\n'); + console.log('✓ Wallet 3 successfully withdrew YELLOW back to channel\n'); } catch (err) { console.log(`⚠ Withdraw Error: ${err}\n`); } @@ -318,7 +414,7 @@ async function main() { intent: AppStateUpdateIntent.Close, version: 3n, allocations: [ - { participant: wallet1Address, asset: 'usdc', amount: new Decimal(0.0001) }, + { participant: wallet1Address, asset: 'yusd', amount: new Decimal(0.0001) }, ], sessionData: '{}', }; @@ -339,8 +435,8 @@ async function main() { intent: AppStateUpdateIntent.Close, version: 5n, allocations: [ - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.00005) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.00001) }, + { participant: wallet2Address, asset: 'yellow', amount: new Decimal(0.00005) }, + { participant: wallet3Address, asset: 'yellow', amount: new Decimal(0.00001) }, ], sessionData: '{}', }; @@ -360,27 +456,32 @@ async function main() { } // --- 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}`); + // Only meaningful when the app registry is enabled — with apps.v1 disabled + // every app ID is "unregistered" from the registry's perspective and the + // node accepts the create call, so the fail-case has nothing to assert. + if (appRegistryEnabled) { + 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 ==='); diff --git a/sdk/ts/examples/app_sessions/package-lock.json b/sdk/ts/examples/app_sessions/package-lock.json index 1e0eef30d..672d38bdf 100644 --- a/sdk/ts/examples/app_sessions/package-lock.json +++ b/sdk/ts/examples/app_sessions/package-lock.json @@ -1,31 +1,34 @@ { - "name": "@nitrolite/example-app-sessions", + "name": "@yellow-org/example-app-sessions", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@nitrolite/example-app-sessions", + "name": "@yellow-org/example-app-sessions", "version": "1.0.0", "dependencies": { - "@nitrolite/sdk": "file:../..", + "@yellow-org/sdk": "file:../..", "decimal.js": "^10.4.3", - "viem": "^2.21.54" + "viem": "^2.50.4", + "ws": "^8.20.1" }, "devDependencies": { "@types/node": "^22.10.2", - "tsx": "^4.19.2", + "@types/ws": "^8.5.13", + "tsx": "^4.22.3", "typescript": "^5.7.2" } }, "../..": { - "name": "@layer-3/nitrolite", - "version": "1.1.0", + "name": "@yellow-org/sdk", + "version": "1.2.2", "license": "MIT", "dependencies": { "abitype": "^1.2.3", "decimal.js": "^10.4.3", - "viem": "^2.46.1", + "jest-util": "^30.3.0", + "viem": "^2.50.4", "zod": "^4.3.6" }, "devDependencies": { @@ -43,11 +46,12 @@ "ethers": "6.16.0", "glob": "^13.0.3", "jest": "^30.2.0", - "prettier": "3.8.1", + "prettier": "3.8.3", "rimraf": "^6.1.3", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.3.0" + "typescript": "^6.0.3", + "ws": "8.20.1" }, "engines": { "node": ">=20.0.0" @@ -60,9 +64,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -77,9 +81,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -94,9 +98,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -111,9 +115,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -128,9 +132,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -145,9 +149,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -162,9 +166,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -179,9 +183,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -196,9 +200,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -213,9 +217,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -230,9 +234,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -247,9 +251,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -264,9 +268,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -281,9 +285,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -298,9 +302,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -315,9 +319,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -332,9 +336,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -349,9 +353,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -366,9 +370,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -383,9 +387,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -400,9 +404,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -417,9 +421,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -434,9 +438,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -451,9 +455,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -468,9 +472,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -485,9 +489,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -501,10 +505,6 @@ "node": ">=18" } }, - "node_modules/@nitrolite/sdk": { - "resolved": "../..", - "link": true - }, "node_modules/@noble/ciphers": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", @@ -590,6 +590,20 @@ "undici-types": "~6.21.0" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@yellow-org/sdk": { + "resolved": "../..", + "link": true + }, "node_modules/abitype": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", @@ -618,9 +632,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -631,32 +645,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/eventemitter3": { @@ -680,19 +694,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/isows": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", @@ -709,9 +710,9 @@ } }, "node_modules/ox": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", - "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.22.tgz", + "integrity": "sha512-nb5msL8qWbPglhIfZbGJAfw3cqiJjFMiWmACt7kgyWtLib12tcctbHufMT9Hb0Lr6Pt4k9I3dbpueTpbhvbqvA==", "funding": [ { "type": "github", @@ -738,25 +739,14 @@ } } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -790,9 +780,9 @@ "license": "MIT" }, "node_modules/viem": { - "version": "2.45.1", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.45.1.tgz", - "integrity": "sha512-LN6Pp7vSfv50LgwhkfSbIXftAM5J89lP9x8TeDa8QM7o41IxlHrDh0F9X+FfnCWtsz11pEVV5sn+yBUoOHNqYA==", + "version": "2.50.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.50.4.tgz", + "integrity": "sha512-rf98F4s3Vlb+uJZEKfay3IbBw3CNCbVtx5Y3UIljlO2tSX420g/J0WQSYsjzBSasUFgxgsXabji14O9kGbiqgg==", "funding": [ { "type": "github", @@ -807,8 +797,8 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.11.3", - "ws": "8.18.3" + "ox": "0.14.22", + "ws": "8.20.1" }, "peerDependencies": { "typescript": ">=5.0.4" @@ -820,9 +810,9 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/sdk/ts/examples/app_sessions/package.json b/sdk/ts/examples/app_sessions/package.json index 8fb08af16..c07f3e39e 100644 --- a/sdk/ts/examples/app_sessions/package.json +++ b/sdk/ts/examples/app_sessions/package.json @@ -9,11 +9,13 @@ "dependencies": { "@yellow-org/sdk": "file:../..", "decimal.js": "^10.4.3", - "viem": "^2.21.54" + "viem": "^2.50.4", + "ws": "^8.20.1" }, "devDependencies": { "@types/node": "^22.10.2", - "tsx": "^4.19.2", + "@types/ws": "^8.5.13", + "tsx": "^4.22.3", "typescript": "^5.7.2" } } diff --git a/sdk/ts/examples/channel_session_key/lifecycle.ts b/sdk/ts/examples/channel_session_key/lifecycle.ts new file mode 100644 index 000000000..34aa8cff2 --- /dev/null +++ b/sdk/ts/examples/channel_session_key/lifecycle.ts @@ -0,0 +1,346 @@ +/** + * Example: Channel Session Key Lifecycle + * + * Requirements to run this example: + * + * 1. A reachable nitronode WebSocket endpoint (set via wsURL below). + * The default points at the stress environment. + * + * 2. One EVM wallet with a hex private key (replace the placeholder below). + * + * 3. Minimum off-chain (channel) balances on the node: + * - 0.00005 YUSD (one deposit + one withdraw via session key) + * - 0.00005 YELLOW (one deposit + one withdraw via session key) + * + * An open channel is NOT a hard prerequisite. If the wallet already has + * funds on the node but no acknowledged channel yet, acknowledge() is run + * first to open one. + * + * 4. chainId below must match the asset's home blockchain for the target + * nitronode deployment, and rpcURL must point at a JSON-RPC endpoint for + * that chain. Both deposit and withdraw are followed by an on-chain + * checkpoint; the example then polls getHomeChannel until the node has + * observed the checkpoint event before moving on. Without a working RPC + * these calls fail. + * + * This example demonstrates: + * 1. Open YUSD and YELLOW channels for the wallet (acknowledge) + * 2. Generate a fresh session key + * 3. Register session key v1 with both assets allowed + * 4. Deposit YUSD and YELLOW via a session-key-backed client (success) + * 5. Update session key v2 -> [YELLOW] only + * 6. Withdraw YELLOW (success); attempt YUSD withdraw via session key (expected fail) + * 7. Update session key v3 -> [YUSD] only + * 8. Withdraw YUSD (success); attempt YELLOW deposit via session key (expected fail) + * 9. Revoke session key v4 -> [] + * 10. Attempt YUSD deposit, YELLOW deposit, and channel closure via session key + * (all expected to fail) + */ + +// Node <22 does not expose a stable global WebSocket. The SDK dialer uses +// `new WebSocket(url)` directly, so we polyfill the global from the `ws` +// package before any client is constructed. +import WebSocket from 'ws'; +if (typeof (globalThis as { WebSocket?: unknown }).WebSocket === 'undefined') { + (globalThis as { WebSocket: unknown }).WebSocket = WebSocket; +} + +import Decimal from 'decimal.js'; +import { Address, Hex } from 'viem'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import { Client } from '../../src/client'; +import { + createSigners, + EthereumMsgSigner, + EthereumRawSigner, + ChannelSessionKeyStateSigner, +} from '../../src/signers'; +import { withBlockchainRPC } from '../../src/config'; +import { ChannelSessionKeyStateV1 } from '../../src/rpc/types'; +import { getChannelSessionKeyAuthMetadataHashV1 } from '../../src/core/utils'; +import { isFinal } from '../../src/core/state'; + +async function main() { + const wsURL = 'wss://nitronode-sandbox.yellow.org/v1/ws'; + + // Replace with your hex private key. The wallet must have minimum off-chain + // balance for YUSD and YELLOW; channels are auto-opened below if missing. + const walletPrivateKey = '0x7d607...' as Hex; + + // chainId is the home blockchain ID used for deposit / withdraw calls. Set + // it to the asset's home chain on the target nitronode deployment. 11155111 + // is Ethereum Sepolia (the stress environment). + const chainId = 11155111n; + + // rpcURL is a JSON-RPC endpoint for chainId. Replace with your own provider + // if the public endpoint is rate-limited. + const rpcURL = 'https://sepolia.drpc.org'; + + // --- Setup wallet signers + wallet-backed SDK client --- + const walletSigners = createSigners(walletPrivateKey); + const walletAddress = walletSigners.stateSigner.getAddress(); + console.log(`Wallet: ${walletAddress}\n`); + + const walletClient = await Client.create( + wsURL, + walletSigners.stateSigner, + walletSigners.txSigner, + withBlockchainRPC(chainId, rpcURL) + ); + + try { + // --- Step 1: ensure YUSD and YELLOW channels are open --- + console.log('=== Step 1: Ensuring channels are open ==='); + await ensureChannelOpen(walletClient, 'yusd'); + await ensureChannelOpen(walletClient, 'yellow'); + console.log(); + + // --- Step 2: generate a fresh session key --- + console.log('=== Step 2: Generating session key ==='); + const sessionKeyPrivateKey = generatePrivateKey(); + const sessionKeyAccount = privateKeyToAccount(sessionKeyPrivateKey); + const sessionKeyAddress = sessionKeyAccount.address; + const sessionKeyMsgSigner = new EthereumMsgSigner(sessionKeyPrivateKey); + console.log(`Session key: ${sessionKeyAddress}\n`); + + // --- Step 3: register session key v1 with both assets allowed --- + console.log('=== Step 3: Registering session key v1 ([yusd, yellow]) ==='); + const stateV1 = await submitSessionKey(walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 1n, ['yusd', 'yellow']); + console.log('✓ v1 registered\n'); + + // --- Step 4: deposit YUSD and YELLOW via session-key client --- + console.log('=== Step 4: Depositing via session-key client (v1) ==='); + const skClient1 = await newSessionKeyClient(wsURL, walletPrivateKey, sessionKeyPrivateKey, stateV1, chainId, rpcURL); + try { + const yusdDeposit = await skClient1.deposit(chainId, 'yusd', new Decimal(0.00001)); + console.log('✓ YUSD deposited via session key'); + await checkpointAndWait(skClient1, 'yusd', yusdDeposit.version); + + const yellowDeposit = await skClient1.deposit(chainId, 'yellow', new Decimal(0.00001)); + console.log('✓ YELLOW deposited via session key'); + await checkpointAndWait(skClient1, 'yellow', yellowDeposit.version); + } finally { + await skClient1.close(); + } + console.log(); + + // --- Step 5: update session key v2 -> [yellow] --- + console.log('=== Step 5: Updating session key v2 ([yellow]) ==='); + const stateV2 = await submitSessionKey(walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 2n, ['yellow']); + const skClient2 = await newSessionKeyClient(wsURL, walletPrivateKey, sessionKeyPrivateKey, stateV2, chainId, rpcURL); + console.log('✓ v2 registered\n'); + + try { + // --- Step 6: withdraw YELLOW (ok); attempt YUSD withdraw (fail) --- + console.log('=== Step 6: Withdraw via v2 (yellow only) ==='); + const yellowWithdraw = await skClient2.withdraw(chainId, 'yellow', new Decimal(0.000005)); + console.log('✓ YELLOW withdrawn via session key'); + await checkpointAndWait(skClient2, 'yellow', yellowWithdraw.version); + + try { + await skClient2.withdraw(chainId, 'yusd', new Decimal(0.000005)); + console.log('✗ Unexpected: YUSD withdraw succeeded under v2'); + } catch (err) { + console.log(`✓ Expected: YUSD withdraw rejected by node: ${err}`); + } + } finally { + await skClient2.close(); + } + console.log(); + + // --- Step 7: update session key v3 -> [yusd] --- + console.log('=== Step 7: Updating session key v3 ([yusd]) ==='); + const stateV3 = await submitSessionKey(walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 3n, ['yusd']); + const skClient3 = await newSessionKeyClient(wsURL, walletPrivateKey, sessionKeyPrivateKey, stateV3, chainId, rpcURL); + console.log('✓ v3 registered\n'); + + try { + // --- Step 8: withdraw YUSD (ok); attempt YELLOW deposit (fail) --- + console.log('=== Step 8: Withdraw via v3 (yusd only) ==='); + const yusdWithdraw = await skClient3.withdraw(chainId, 'yusd', new Decimal(0.000005)); + console.log('✓ YUSD withdrawn via session key'); + await checkpointAndWait(skClient3, 'yusd', yusdWithdraw.version); + + try { + await skClient3.deposit(chainId, 'yellow', new Decimal(0.000005)); + console.log('✗ Unexpected: YELLOW deposit succeeded under v3'); + } catch (err) { + console.log(`✓ Expected: YELLOW deposit rejected by node: ${err}`); + } + } finally { + await skClient3.close(); + } + console.log(); + + // --- Step 9: revoke session key v4 -> [] --- + // Empty assets disables every per-asset check on the node, so the next + // version of the key cannot authorize any channel operation. + console.log('=== Step 9: Revoking session key v4 (empty assets) ==='); + const stateV4 = await submitSessionKey(walletClient, walletAddress, sessionKeyAddress, sessionKeyMsgSigner, 4n, []); + const skClient4 = await newSessionKeyClient(wsURL, walletPrivateKey, sessionKeyPrivateKey, stateV4, chainId, rpcURL); + console.log('✓ v4 registered (revoked)\n'); + + try { + // --- Step 10: every session-key operation must fail --- + console.log('=== Step 10: Verifying revoked session key cannot operate ==='); + try { + await skClient4.deposit(chainId, 'yusd', new Decimal(0.000005)); + console.log('✗ Unexpected: YUSD deposit succeeded under v4'); + } catch (err) { + console.log(`✓ Expected: YUSD deposit rejected by node: ${err}`); + } + try { + await skClient4.deposit(chainId, 'yellow', new Decimal(0.000005)); + console.log('✗ Unexpected: YELLOW deposit succeeded under v4'); + } catch (err) { + console.log(`✓ Expected: YELLOW deposit rejected by node: ${err}`); + } + try { + await skClient4.closeHomeChannel('yusd'); + console.log('✗ Unexpected: YUSD channel close succeeded under v4'); + } catch (err) { + console.log(`✓ Expected: YUSD channel close rejected by node: ${err}`); + } + } finally { + await skClient4.close(); + } + + console.log('\n=== Example Complete ==='); + } finally { + await walletClient.close(); + } +} + +/** + * ensureChannelOpen guarantees that the wallet has an acknowledged channel + * open for asset. If the node holds no state for the wallet/asset pair, or + * the latest state is still awaiting the user's signature (or has been + * finalized), acknowledge() is invoked to create or progress the channel. + */ +async function ensureChannelOpen(client: Client, asset: string): Promise { + const wallet = client.getUserAddress(); + const state = await client.getLatestState(wallet, asset, false); + + const hasOpenChannel = + state !== null && + state.homeChannelId !== undefined && + !isFinal(state) && + !!state.userSig; + if (hasOpenChannel) { + console.log(`✓ channel already open for ${asset}`); + return; + } + + await client.acknowledge(asset); + console.log(`✓ acknowledged channel for ${asset}`); +} + +/** + * submitSessionKey signs and submits a (version, assets) update for the + * channel session key using the wallet client. Returns the registered state + * (including user_sig + session_key_sig) so the caller can derive the matching + * session-key state signer for subsequent operations. + */ +async function submitSessionKey( + walletClient: Client, + walletAddress: Address, + sessionKeyAddress: Address, + sessionKeyMsgSigner: EthereumMsgSigner, + version: bigint, + assets: string[] +): Promise { + const expiresAt = BigInt(Math.floor(Date.now() / 1000) + 24 * 60 * 60); + + const state: ChannelSessionKeyStateV1 = { + user_address: walletAddress, + session_key: sessionKeyAddress, + version: version.toString(), + assets, + expires_at: expiresAt.toString(), + user_sig: '', + session_key_sig: '', + }; + + state.user_sig = await walletClient.signChannelSessionKeyState(state); + state.session_key_sig = await walletClient.signChannelSessionKeyOwnership(state, sessionKeyMsgSigner); + + await walletClient.submitChannelSessionKeyState(state); + return state; +} + +/** + * newSessionKeyClient builds an SDK client whose state signer is the channel + * session key derived from the registered state. All channel state operations + * (deposit, withdraw, closeHomeChannel, …) issued through this client are + * signed with the session key, and the node validates them against the latest + * registered (user, session_key, version) tuple — including the asset + * allow-list and expiry. + * + * walletPrivateKey must remain the wallet's key for the txSigner: the SDK + * uses txSigner to sign on-chain checkpoint transactions, and ChannelHub will + * only accept calls from the channel's user. Substituting the session key + * here would either point the client at the wrong on-chain identity or fail + * tx-level auth. + */ +async function newSessionKeyClient( + wsURL: string, + walletPrivateKey: Hex, + sessionKeyPrivateKey: Hex, + state: ChannelSessionKeyStateV1, + chainId: bigint, + rpcURL: string +): Promise { + const walletAddress = privateKeyToAccount(walletPrivateKey).address; + + const metadataHash = getChannelSessionKeyAuthMetadataHashV1( + walletAddress, + BigInt(state.version), + state.assets, + BigInt(state.expires_at) + ); + + // user_sig is stripped of any ChannelSigner type-byte prefix by + // Client.signChannelSessionKeyState before submission, so the value we have + // here is already the raw EIP-191 signature expected by the signer. + const stateSigner = new ChannelSessionKeyStateSigner( + sessionKeyPrivateKey, + walletAddress, + metadataHash, + state.user_sig as Hex + ); + + const txSigner = new EthereumRawSigner(walletPrivateKey); + + return await Client.create(wsURL, stateSigner, txSigner, withBlockchainRPC(chainId, rpcURL)); +} + +/** + * checkpointAndWait runs checkpoint() for asset and polls getHomeChannel + * until the node's observed on-chain state_version catches up to + * expectedVersion. Without this barrier the next deposit/withdraw can race + * the node's event ingestion and be rejected with "home deposit is still + * ongoing". + */ +async function checkpointAndWait(client: Client, asset: string, expectedVersion: bigint): Promise { + const txHash = await client.checkpoint(asset); + console.log(` ↳ checkpoint ${asset} tx ${txHash} submitted; waiting for node to observe state_version=${expectedVersion}...`); + + const wallet = client.getUserAddress(); + const deadline = Date.now() + 2 * 60 * 1000; + while (true) { + const channel = await client.getHomeChannel(wallet, asset); + if (channel !== null && channel.stateVersion >= expectedVersion) { + console.log(` ↳ node observed state_version=${channel.stateVersion} for ${asset}`); + return; + } + if (Date.now() > deadline) { + throw new Error(`timed out waiting for ${asset} to reach state_version=${expectedVersion}`); + } + await new Promise((resolve) => setTimeout(resolve, 2000)); + } +} + +main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/sdk/ts/examples/channel_session_key/package-lock.json b/sdk/ts/examples/channel_session_key/package-lock.json new file mode 100644 index 000000000..0b69e8f84 --- /dev/null +++ b/sdk/ts/examples/channel_session_key/package-lock.json @@ -0,0 +1,834 @@ +{ + "name": "@yellow-org/example-channel-session-key", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@yellow-org/example-channel-session-key", + "version": "1.0.0", + "dependencies": { + "@yellow-org/sdk": "file:../..", + "decimal.js": "^10.4.3", + "viem": "^2.46.2", + "ws": "^8.18.3" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/ws": "^8.5.13", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } + }, + "../..": { + "name": "@yellow-org/sdk", + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "abitype": "^1.2.3", + "decimal.js": "^10.4.3", + "jest-util": "^30.3.0", + "viem": "^2.46.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@ethereumjs/blockchain": "^10.0.0", + "@ethereumjs/common": "^10.0.0", + "@ethereumjs/evm": "^10.0.0", + "@ethereumjs/statemanager": "^10.0.0", + "@ethereumjs/util": "^10.0.0", + "@ethereumjs/vm": "^10.0.0", + "@types/jest": "30.0.0", + "@types/node": "^25.2.3", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", + "eslint": "^10.0.0", + "ethers": "6.16.0", + "glob": "^13.0.3", + "jest": "^30.2.0", + "prettier": "3.8.3", + "rimraf": "^6.1.3", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "^6.0.3", + "ws": "8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@yellow-org/sdk": { + "resolved": "../..", + "link": true + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/ox": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.22.tgz", + "integrity": "sha512-nb5msL8qWbPglhIfZbGJAfw3cqiJjFMiWmACt7kgyWtLib12tcctbHufMT9Hb0Lr6Pt4k9I3dbpueTpbhvbqvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.50.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.50.4.tgz", + "integrity": "sha512-rf98F4s3Vlb+uJZEKfay3IbBw3CNCbVtx5Y3UIljlO2tSX420g/J0WQSYsjzBSasUFgxgsXabji14O9kGbiqgg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.22", + "ws": "8.20.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "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 + } + } + } + } +} diff --git a/sdk/ts/examples/channel_session_key/package.json b/sdk/ts/examples/channel_session_key/package.json new file mode 100644 index 000000000..3771618b0 --- /dev/null +++ b/sdk/ts/examples/channel_session_key/package.json @@ -0,0 +1,21 @@ +{ + "name": "@yellow-org/example-channel-session-key", + "version": "1.0.0", + "description": "Nitrolite SDK Channel Session Key Lifecycle Example", + "type": "module", + "scripts": { + "lifecycle": "tsx lifecycle.ts" + }, + "dependencies": { + "@yellow-org/sdk": "file:../..", + "decimal.js": "^10.4.3", + "viem": "^2.46.2", + "ws": "^8.18.3" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/ws": "^8.5.13", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } +} diff --git a/sdk/ts/examples/channel_session_key/tsconfig.json b/sdk/ts/examples/channel_session_key/tsconfig.json new file mode 100644 index 000000000..da18e0fd0 --- /dev/null +++ b/sdk/ts/examples/channel_session_key/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2022", + "lib": ["ES2022"], + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["*.ts"], + "exclude": ["node_modules"] +} diff --git a/sdk/ts/examples/example-app/README.md b/sdk/ts/examples/example-app/README.md index f1d4011e9..5d93f6f72 100644 --- a/sdk/ts/examples/example-app/README.md +++ b/sdk/ts/examples/example-app/README.md @@ -23,7 +23,7 @@ npm install @layer-3/nitrolite viem decimal.js ## Core Concepts -The SDK connects to a **clearnode** (a state channel server) over WebSocket. All operations (deposits, withdrawals, transfers) happen off-chain as signed state updates. You can **checkpoint** at any time to sync state on-chain. +The SDK connects to a **nitronode** (a state channel server) over WebSocket. All operations (deposits, withdrawals, transfers) happen off-chain as signed state updates. You can **checkpoint** at any time to sync state on-chain. The client needs two signers: @@ -102,7 +102,7 @@ const txSigner = new WalletTransactionSigner(walletClient); // Connect const client = await Client.create( - 'wss://clearnode-sandbox.yellow.org/v1/ws', + 'wss://nitronode-sandbox.yellow.org/v1/ws', stateSigner, txSigner, withBlockchainRPC(11155111n, 'https://ethereum-sepolia-rpc.publicnode.com'), @@ -224,7 +224,7 @@ await client.close(); ## Session Keys (Auto-Sign) -Session keys let your app sign state updates automatically without wallet popups on every operation. A temporary key is generated in the browser, registered with the clearnode, and used for a limited time. +Session keys let your app sign state updates automatically without wallet popups on every operation. A temporary key is generated in the browser, registered with the nitronode, and used for a limited time. ### Enable @@ -232,6 +232,7 @@ Session keys let your app sign state updates automatically without wallet popups import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { ChannelSessionKeyStateSigner, + EthereumMsgSigner, getChannelSessionKeyAuthMetadataHashV1, } from '@layer-3/nitrolite'; @@ -253,14 +254,19 @@ const state = { assets: ['usdc', 'weth'], expires_at: expiresAt.toString(), user_sig: '', + session_key_sig: '', }; -// 4. Sign with the main wallet and submit +// 4. Sign ownership with the session key, then user_sig with the main wallet, then submit. +// Both signatures are required; the server rejects submits with either missing. +const sessionKeySigner = new EthereumMsgSigner(privateKey); +state.session_key_sig = await client.signChannelSessionKeyOwnership(state, sessionKeySigner); state.user_sig = await client.signChannelSessionKeyState(state); await client.submitChannelSessionKeyState(state); // 5. Compute the metadata hash const metadataHash = getChannelSessionKeyAuthMetadataHashV1( + address, version, ['usdc', 'weth'], expiresAt, @@ -276,7 +282,7 @@ const sessionSigner = new ChannelSessionKeyStateSigner( const txSigner = new WalletTransactionSigner(walletClient); const sessionClient = await Client.create( - 'wss://clearnode-sandbox.yellow.org/v1/ws', + 'wss://nitronode-sandbox.yellow.org/v1/ws', sessionSigner, txSigner, withBlockchainRPC(11155111n, 'https://ethereum-sepolia-rpc.publicnode.com'), @@ -287,27 +293,39 @@ Now `sessionClient` signs off-chain state updates with the session key — no wa ### Revoke -Submit a new version with empty assets to revoke a session key: +Submit a new version with `expires_at` in the past to revoke a session key. The nitronode +treats any submit whose `expires_at <= now` as a revocation: the slot is freed for the +per-user cap, and the auth path stops accepting state signed by the key. ```ts -import { packChannelKeyStateV1 } from '@layer-3/nitrolite'; +import { EthereumMsgSigner, packChannelKeyStateV1 } from '@layer-3/nitrolite'; const existing = await client.getLastChannelKeyStates(address, sessionKeyAddress); const latest = existing[0]; +// expires_at one second before now is sufficient; the server compares against its own clock. +const pastExpiresAt = (Math.floor(Date.now() / 1000) - 1).toString(); + const revokeState = { user_address: address, session_key: sessionKeyAddress, version: (BigInt(latest.version) + 1n).toString(), - assets: [], - expires_at: latest.expires_at, + assets: latest.assets, + expires_at: pastExpiresAt, user_sig: '', + session_key_sig: '', }; +// Revoke still requires the session-key holder's possession proof — sign with the session key +// (the holder is consenting to retire it) before submit. +const sessionKeySigner = new EthereumMsgSigner(sessionKeyPrivateKey); +revokeState.session_key_sig = await client.signChannelSessionKeyOwnership(revokeState, sessionKeySigner); + // Sign the revocation with the main wallet (EIP-191) const metadataHash = getChannelSessionKeyAuthMetadataHashV1( + address, BigInt(revokeState.version), - [], + revokeState.assets, BigInt(revokeState.expires_at), ); revokeState.user_sig = await walletClient.signMessage({ diff --git a/sdk/ts/examples/example-app/package-lock.json b/sdk/ts/examples/example-app/package-lock.json index 48ff3c2ac..89949d795 100644 --- a/sdk/ts/examples/example-app/package-lock.json +++ b/sdk/ts/examples/example-app/package-lock.json @@ -15,32 +15,32 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "decimal.js": "^10.4.3", - "lucide-react": "^0.563.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwind-merge": "^3.4.0", - "viem": "^2.39.3" + "lucide-react": "^1.16.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "tailwind-merge": "^3.6.0", + "viem": "^2.50.4" }, "devDependencies": { - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.17", + "@types/react": "^19.2.15", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "autoprefixer": "^10.5.0", + "postcss": "^8.5.15", + "tailwindcss": "^4.3.0", "typescript": "^5.3.0", - "vite": "^6.0.3" + "vite": "^8.0.14" } }, "../..": { "name": "@yellow-org/sdk", - "version": "1.2.0", + "version": "1.2.2", "license": "MIT", "dependencies": { "abitype": "^1.2.3", "decimal.js": "^10.4.3", - "jest-util": "^30.2.0", - "viem": "^2.46.1", + "jest-util": "^30.3.0", + "viem": "^2.50.4", "zod": "^4.3.6" }, "devDependencies": { @@ -58,11 +58,12 @@ "ethers": "6.16.0", "glob": "^13.0.3", "jest": "^30.2.0", - "prettier": "3.8.1", + "prettier": "3.8.3", "rimraf": "^6.1.3", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.3.0" + "typescript": "^6.0.3", + "ws": "8.21.0" }, "engines": { "node": ">=20.0.0" @@ -74,407 +75,467 @@ "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@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" + "tslib": "^2.4.0" } }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@tybys/wasm-util": "^0.10.1" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "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==", - "dev": true, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "node": "^14.21.3 || >=16" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@noble/hashes": "1.8.0" }, "engines": { - "node": ">=6.9.0" + "node": "^14.21.3 || >=16" }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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==", - "dev": true, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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==", + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "bin": { - "parser": "bin/babel-parser.js" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=6.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=6.9.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=6.9.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=6.9.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", "cpu": [ "arm64" ], @@ -482,69 +543,69 @@ "license": "MIT", "optional": true, "os": [ - "freebsd" + "android" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", "cpu": [ - "arm" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", "cpu": [ - "ia32" + "arm" ], "dev": true, "license": "MIT", @@ -553,15 +614,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", "cpu": [ - "loong64" + "arm64" ], "dev": true, "license": "MIT", @@ -570,15 +631,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", "cpu": [ - "mips64el" + "arm64" ], "dev": true, "license": "MIT", @@ -587,13 +648,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", "cpu": [ "ppc64" ], @@ -604,15 +665,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", "cpu": [ - "riscv64" + "s390x" ], "dev": true, "license": "MIT", @@ -621,15 +682,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", "cpu": [ - "s390x" + "x64" ], "dev": true, "license": "MIT", @@ -638,13 +699,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", "cpu": [ "x64" ], @@ -655,13 +716,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", "cpu": [ "arm64" ], @@ -669,33 +730,35 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "openharmony" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", "cpu": [ - "x64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", "cpu": [ "arm64" ], @@ -703,16 +766,16 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", "cpu": [ "x64" ], @@ -720,2063 +783,731 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", - "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collapsible": "1.1.12", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/@yellow-org/sdk": { - "resolved": "../..", - "link": true - }, - "node_modules/abitype": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", - "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3.22.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/autoprefixer": { - "version": "10.4.24", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", - "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", - "dev": true, - "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.28.1", - "caniuse-lite": "^1.0.30001766", - "fraction.js": "^5.3.4", - "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/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==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001768", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", - "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", - "dev": true, - "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/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "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/chokidar/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==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 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==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "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==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "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==", - "dev": true, - "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-glob/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==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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==", - "dev": true, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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==", - "dev": true, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", "license": "MIT", - "engines": { - "node": "*" + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "tslib": "^2.4.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==", - "dev": true, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "devOptional": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "csstype": "^3.2.2" } }, - "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==", - "dev": true, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@types/react": "^19.2.0" } }, - "node_modules/glob-parent": { + "node_modules/@vitejs/plugin-react": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "@rolldown/pluginutils": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, - "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==", - "dev": true, + "node_modules/@yellow-org/sdk": { + "resolved": "../..", + "link": true + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" + "funding": { + "url": "https://github.com/sponsors/wevm" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } } }, - "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==", + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, + "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": { - "hasown": "^2.0.2" + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" }, "engines": { - "node": ">= 0.4" + "node": "^10 || ^12 || >=14" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "postcss": "^8.1.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==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "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==", + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, + "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": { - "is-extglob": "^2.1.1" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=0.10.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "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==", + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", "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/wevm" + "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "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==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "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==", - "license": "MIT" + "license": "CC-BY-4.0" }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://polar.sh/cva" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, "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==", + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" + "node": ">=8" } }, - "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==", + "node_modules/electron-to-chromium": { + "version": "1.5.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", + "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", "dev": true, - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } + "license": "ISC" }, - "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==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/lucide-react": { - "version": "0.563.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", - "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "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==", "dev": true, "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, "engines": { - "node": ">=8.6" + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" } }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/ai" + "url": "https://github.com/sponsors/wevm" } ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "peerDependencies": { + "ws": "*" } }, - "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==", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, - "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": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "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==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/ox": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", - "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.2.3", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "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==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "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/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.6" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "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": "MPL-2.0", + "optional": true, + "os": [ + "win32" ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14.0.0" + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", + "license": "ISC", "peerDependencies": { - "postcss": "^8.0.0" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "dev": true, + "license": "MIT" + }, + "node_modules/ox": { + "version": "0.14.25", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.25.tgz", + "integrity": "sha512-8DoibKtxE8yw63Y2jjMhlbjaURev6WCx4QR4MWLusl2/qIaeTzMJMBIYIDl1KOF45+8H1Ur6eLTdPlUoO8PlRw==", "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, { "type": "github", - "url": "https://github.com/sponsors/ai" + "url": "https://github.com/sponsors/wevm" } ], "license": "MIT", "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" }, "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "typescript": ">=5.4.0" }, "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { + "typescript": { "optional": true } } }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "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" @@ -2784,27 +1515,12 @@ ], "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=4" + "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-value-parser": { @@ -2814,204 +1530,66 @@ "dev": true, "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==", - "dev": true, - "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/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" + "react": "^19.2.6" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "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/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "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==", - "dev": true, - "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" + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "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/source-map-js": { "version": "1.2.1", @@ -3023,46 +1601,10 @@ "node": ">=0.10.0" } }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tailwind-merge": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", - "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", "license": "MIT", "funding": { "type": "github", @@ -3070,75 +1612,21 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } + "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -3147,56 +1635,13 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "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==", "dev": true, - "license": "Apache-2.0" + "license": "0BSD", + "optional": true }, "node_modules/typescript": { "version": "5.9.3", @@ -3243,17 +1688,10 @@ "browserslist": ">= 4.21.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==", - "dev": true, - "license": "MIT" - }, "node_modules/viem": { - "version": "2.45.1", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.45.1.tgz", - "integrity": "sha512-LN6Pp7vSfv50LgwhkfSbIXftAM5J89lP9x8TeDa8QM7o41IxlHrDh0F9X+FfnCWtsz11pEVV5sn+yBUoOHNqYA==", + "version": "2.51.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.51.2.tgz", + "integrity": "sha512-2x4YAtr3PUPIW++Ov96clnWtRsyqMfpFfooQRIxCpAMsTgxioJTdIQ0ywbjhlHDCUJEGM6M8q8ILOeaPRViH9w==", "funding": [ { "type": "github", @@ -3268,8 +1706,8 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.11.3", - "ws": "8.18.3" + "ox": "0.14.25", + "ws": "8.20.1" }, "peerDependencies": { "typescript": ">=5.0.4" @@ -3281,24 +1719,23 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -3307,14 +1744,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -3323,13 +1761,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { @@ -3355,41 +1796,10 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -3406,13 +1816,6 @@ "optional": true } } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" } } } diff --git a/sdk/ts/examples/example-app/package.json b/sdk/ts/examples/example-app/package.json index 1fd308e15..961443e95 100644 --- a/sdk/ts/examples/example-app/package.json +++ b/sdk/ts/examples/example-app/package.json @@ -17,20 +17,20 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "decimal.js": "^10.4.3", - "lucide-react": "^0.563.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwind-merge": "^3.4.0", - "viem": "^2.39.3" + "lucide-react": "^1.16.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "tailwind-merge": "^3.6.0", + "viem": "^2.50.4" }, "devDependencies": { - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.17", + "@types/react": "^19.2.15", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "autoprefixer": "^10.5.0", + "postcss": "^8.5.15", + "tailwindcss": "^4.3.0", "typescript": "^5.3.0", - "vite": "^6.0.3" + "vite": "^8.0.14" } } diff --git a/sdk/ts/examples/example-app/src/App.tsx b/sdk/ts/examples/example-app/src/App.tsx index 4c481b8ed..56280d004 100644 --- a/sdk/ts/examples/example-app/src/App.tsx +++ b/sdk/ts/examples/example-app/src/App.tsx @@ -6,11 +6,11 @@ import { mainnet } from 'viem/chains'; import { WalletStateSigner, WalletTransactionSigner } from './walletSigners'; import WalletDashboard from './components/WalletDashboard'; import StatusBar from './components/StatusBar'; -import type { AppState, ClearnodeConfig, NetworkConfig, SessionKeyState, StatusMessage } from './types'; +import type { AppState, NitronodeConfig, NetworkConfig, SessionKeyState, StatusMessage } from './types'; import { formatAddress } from './utils'; -const CLEARNODES: ClearnodeConfig[] = [ - { name: 'YN Testnet', url: 'wss://clearnode-sandbox.yellow.org/v1/ws' }, +const NITRONODES: NitronodeConfig[] = [ + { name: 'YN Testnet', url: 'wss://nitronode-sandbox.yellow.org/v1/ws' }, ]; const NETWORKS: NetworkConfig[] = [ @@ -29,7 +29,7 @@ function App() { client: null, address: null, connected: false, - nodeUrl: CLEARNODES[0].url, + nodeUrl: NITRONODES[0].url, selectedChainId: NETWORKS[0].chainId, selectedAsset: '', sessionKey, @@ -130,7 +130,7 @@ function App() { if (raw) storedSk = JSON.parse(raw); } catch { /* ignore */ } - const sdkClient = await buildClient(wc, CLEARNODES[0].url, storedSk, address); + const sdkClient = await buildClient(wc, NITRONODES[0].url, storedSk, address); if (storedSk && !(storedSk.active && storedSk.metadataHash && storedSk.authSig)) { storedSk = { ...storedSk, active: false }; } @@ -209,8 +209,8 @@ function App() { const sdkClient = await buildClient(wc, appState.nodeUrl, appState.sessionKey, address); setAppState(prev => ({ ...prev, address, client: sdkClient, connected: true })); await fetchAssets(sdkClient); - const node = CLEARNODES.find(c => c.url === appState.nodeUrl); - showStatus('success', `Connected to ${node?.name || 'Clearnode'}`); + const node = NITRONODES.find(c => c.url === appState.nodeUrl); + showStatus('success', `Connected to ${node?.name || 'Nitronode'}`); } catch (nodeError) { console.error('Node connection failed:', nodeError); setAppState(prev => ({ ...prev, address, connected: false })); @@ -385,7 +385,7 @@ function App() { Connection Failed

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